From 7c99e02eee8cea2b15d4839009cd79ecb00297bc Mon Sep 17 00:00:00 2001
From: entholzer
Date: Sun, 4 Aug 2024 14:30:39 +0200
Subject: [PATCH 01/50] add VcsAccessLog table
---
.../in/www1/artemis/domain/VcsAccessLog.java | 54 +++++++++++++++++++
.../repository/VcsAccessLogRepository.java | 23 ++++++++
.../artemis/service/VcsAccessLogService.java | 36 +++++++++++++
.../localvc/LocalVCServletService.java | 12 ++++-
.../changelog/20240804144500_changelog.xml | 47 ++++++++++++++++
.../resources/config/liquibase/master.xml | 2 +
6 files changed, 172 insertions(+), 2 deletions(-)
create mode 100644 src/main/java/de/tum/in/www1/artemis/domain/VcsAccessLog.java
create mode 100644 src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
create mode 100644 src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
create mode 100644 src/main/resources/config/liquibase/changelog/20240804144500_changelog.xml
diff --git a/src/main/java/de/tum/in/www1/artemis/domain/VcsAccessLog.java b/src/main/java/de/tum/in/www1/artemis/domain/VcsAccessLog.java
new file mode 100644
index 000000000000..809fb37ae541
--- /dev/null
+++ b/src/main/java/de/tum/in/www1/artemis/domain/VcsAccessLog.java
@@ -0,0 +1,54 @@
+package de.tum.in.www1.artemis.domain;
+
+import java.time.ZonedDateTime;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
+
+import org.hibernate.annotations.Cache;
+import org.hibernate.annotations.CacheConcurrencyStrategy;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+import de.tum.in.www1.artemis.domain.participation.Participation;
+
+/**
+ * A Result.
+ */
+@Entity
+@Table(name = "vcs_access_log")
+@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
+public class VcsAccessLog extends DomainObject {
+
+ @ManyToOne
+ private User student;
+
+ @ManyToOne
+ private Participation participation;
+
+ @Column(name = "operation")
+ private String operation;
+
+ @Column(name = "mechanism")
+ private String mechanism;
+
+ @Column(name = "ipAddress")
+ private String ipAddress;
+
+ @Column(name = "timestamp")
+ private ZonedDateTime timestamp;
+
+ public VcsAccessLog(User user, Participation participation, String operation, String mechanism, String ipAddress) {
+ this.student = user;
+ this.participation = (Participation) participation;
+ this.operation = operation;
+ this.mechanism = mechanism;
+ this.ipAddress = ipAddress;
+ }
+
+ public VcsAccessLog() {
+ }
+}
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
new file mode 100644
index 000000000000..da2b5bf78df7
--- /dev/null
+++ b/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
@@ -0,0 +1,23 @@
+package de.tum.in.www1.artemis.repository;
+
+import static de.tum.in.www1.artemis.config.Constants.PROFILE_CORE;
+
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Repository;
+
+import de.tum.in.www1.artemis.domain.VcsAccessLog;
+import de.tum.in.www1.artemis.repository.base.ArtemisJpaRepository;
+
+/**
+ * Spring Data JPA repository for the User entity.
+ *
+ *
+ * Note: Please keep in mind that the User entities are soft-deleted when adding new queries to this repository.
+ * If you don't need deleted user entities, add `WHERE user.isDeleted = FALSE` to your query.
+ *
+ */
+@Profile(PROFILE_CORE)
+@Repository
+public interface VcsAccessLogRepository extends ArtemisJpaRepository {
+
+}
diff --git a/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java b/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
new file mode 100644
index 000000000000..fd8afa016b3e
--- /dev/null
+++ b/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
@@ -0,0 +1,36 @@
+package de.tum.in.www1.artemis.service;
+
+import static de.tum.in.www1.artemis.config.Constants.PROFILE_LOCALVC;
+
+import jakarta.servlet.http.HttpServletRequest;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Profile;
+import org.springframework.stereotype.Service;
+
+import de.tum.in.www1.artemis.domain.User;
+import de.tum.in.www1.artemis.domain.VcsAccessLog;
+import de.tum.in.www1.artemis.domain.participation.Participation;
+import de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseParticipation;
+import de.tum.in.www1.artemis.repository.VcsAccessLogRepository;
+
+@Profile(PROFILE_LOCALVC)
+@Service
+public class VcsAccessLogService {
+
+ private static final Logger log = LoggerFactory.getLogger(VcsAccessLogService.class);
+
+ private final VcsAccessLogRepository vcsAccessLogRepository;
+
+ VcsAccessLogService(VcsAccessLogRepository vcsAccessLogRepository) {
+ this.vcsAccessLogRepository = vcsAccessLogRepository;
+ }
+
+ public void storeOperation(User user, ProgrammingExerciseParticipation participation, String requestURI, String authenticationMechanism, HttpServletRequest request) {
+ log.debug("Storing access operation for user {}", user);
+
+ VcsAccessLog accessLogEntry = new VcsAccessLog(user, (Participation) participation, "op", "mec", "ip");
+ vcsAccessLogRepository.save(accessLogEntry);
+ }
+}
diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java
index 684149574974..370f23714f2d 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java
@@ -48,6 +48,7 @@
import de.tum.in.www1.artemis.repository.UserRepository;
import de.tum.in.www1.artemis.security.SecurityUtils;
import de.tum.in.www1.artemis.service.AuthorizationCheckService;
+import de.tum.in.www1.artemis.service.VcsAccessLogService;
import de.tum.in.www1.artemis.service.connectors.ci.ContinuousIntegrationTriggerService;
import de.tum.in.www1.artemis.service.programming.AuxiliaryRepositoryService;
import de.tum.in.www1.artemis.service.programming.ProgrammingExerciseParticipationService;
@@ -93,6 +94,8 @@ public class LocalVCServletService {
private final ProgrammingTriggerService programmingTriggerService;
+ private final VcsAccessLogService vcsAccessLogService;
+
private static URL localVCBaseUrl;
@Value("${artemis.version-control.url}")
@@ -122,7 +125,7 @@ public LocalVCServletService(AuthenticationManager authenticationManager, UserRe
RepositoryAccessService repositoryAccessService, AuthorizationCheckService authorizationCheckService,
ProgrammingExerciseParticipationService programmingExerciseParticipationService, AuxiliaryRepositoryService auxiliaryRepositoryService,
ContinuousIntegrationTriggerService ciTriggerService, ProgrammingSubmissionService programmingSubmissionService,
- ProgrammingMessagingService programmingMessagingService, ProgrammingTriggerService programmingTriggerService) {
+ ProgrammingMessagingService programmingMessagingService, ProgrammingTriggerService programmingTriggerService, VcsAccessLogService vcsAccessLogService) {
this.authenticationManager = authenticationManager;
this.userRepository = userRepository;
this.programmingExerciseRepository = programmingExerciseRepository;
@@ -134,6 +137,7 @@ public LocalVCServletService(AuthenticationManager authenticationManager, UserRe
this.programmingSubmissionService = programmingSubmissionService;
this.programmingMessagingService = programmingMessagingService;
this.programmingTriggerService = programmingTriggerService;
+ this.vcsAccessLogService = vcsAccessLogService;
}
/**
@@ -204,7 +208,7 @@ public void authenticateAndAuthorizeGitRequest(HttpServletRequest request, Repos
return;
}
}
-
+ String authenticationMechanism = ""; // todo get actual authentication mechanism here: Password / Participation Token / User token
User user = authenticateUser(authorizationHeader);
// Optimization.
@@ -232,6 +236,10 @@ public void authenticateAndAuthorizeGitRequest(HttpServletRequest request, Repos
request.setAttribute("user", user);
+ // Storing the access to the repository in the VCS access log
+ ProgrammingExerciseParticipation participation = null; // Todo get actual participation
+ vcsAccessLogService.storeOperation(user, participation, request.getRequestURI(), authenticationMechanism, request);
+
log.debug("Authorizing user {} for repository {} took {}", user.getLogin(), localVCRepositoryUri, TimeLogUtil.formatDurationFrom(timeNanoStart));
}
diff --git a/src/main/resources/config/liquibase/changelog/20240804144500_changelog.xml b/src/main/resources/config/liquibase/changelog/20240804144500_changelog.xml
new file mode 100644
index 000000000000..10d82d986557
--- /dev/null
+++ b/src/main/resources/config/liquibase/changelog/20240804144500_changelog.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml
index ab3037c89fad..f5d061eaa973 100644
--- a/src/main/resources/config/liquibase/master.xml
+++ b/src/main/resources/config/liquibase/master.xml
@@ -19,6 +19,8 @@
+
+
From 7030993418982d5f874ce6970468bd5bfefb66a3 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Wed, 7 Aug 2024 10:20:59 +0200
Subject: [PATCH 02/50] added name and email to log entry, improved database
entry efficiency
---
.../in/www1/artemis/domain/VcsAccessLog.java | 54 --------------
.../vcstokens/AuthenticationMechanism.java | 20 ++++++
.../domain/vcstokens/VcsAccessLog.java | 71 +++++++++++++++++++
.../repository/VcsAccessLogRepository.java | 2 +-
.../artemis/service/VcsAccessLogService.java | 19 +++--
.../localvc/LocalVCServletService.java | 41 ++++++++---
.../icl/SshGitLocationResolverService.java | 4 +-
.../changelog/20240804144500_changelog.xml | 24 +++----
8 files changed, 151 insertions(+), 84 deletions(-)
delete mode 100644 src/main/java/de/tum/in/www1/artemis/domain/VcsAccessLog.java
create mode 100644 src/main/java/de/tum/in/www1/artemis/domain/vcstokens/AuthenticationMechanism.java
create mode 100644 src/main/java/de/tum/in/www1/artemis/domain/vcstokens/VcsAccessLog.java
diff --git a/src/main/java/de/tum/in/www1/artemis/domain/VcsAccessLog.java b/src/main/java/de/tum/in/www1/artemis/domain/VcsAccessLog.java
deleted file mode 100644
index 809fb37ae541..000000000000
--- a/src/main/java/de/tum/in/www1/artemis/domain/VcsAccessLog.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package de.tum.in.www1.artemis.domain;
-
-import java.time.ZonedDateTime;
-
-import jakarta.persistence.Column;
-import jakarta.persistence.Entity;
-import jakarta.persistence.ManyToOne;
-import jakarta.persistence.Table;
-
-import org.hibernate.annotations.Cache;
-import org.hibernate.annotations.CacheConcurrencyStrategy;
-
-import com.fasterxml.jackson.annotation.JsonInclude;
-
-import de.tum.in.www1.artemis.domain.participation.Participation;
-
-/**
- * A Result.
- */
-@Entity
-@Table(name = "vcs_access_log")
-@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
-@JsonInclude(JsonInclude.Include.NON_EMPTY)
-public class VcsAccessLog extends DomainObject {
-
- @ManyToOne
- private User student;
-
- @ManyToOne
- private Participation participation;
-
- @Column(name = "operation")
- private String operation;
-
- @Column(name = "mechanism")
- private String mechanism;
-
- @Column(name = "ipAddress")
- private String ipAddress;
-
- @Column(name = "timestamp")
- private ZonedDateTime timestamp;
-
- public VcsAccessLog(User user, Participation participation, String operation, String mechanism, String ipAddress) {
- this.student = user;
- this.participation = (Participation) participation;
- this.operation = operation;
- this.mechanism = mechanism;
- this.ipAddress = ipAddress;
- }
-
- public VcsAccessLog() {
- }
-}
diff --git a/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/AuthenticationMechanism.java b/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/AuthenticationMechanism.java
new file mode 100644
index 000000000000..815964e49ae3
--- /dev/null
+++ b/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/AuthenticationMechanism.java
@@ -0,0 +1,20 @@
+package de.tum.in.www1.artemis.domain.vcstokens;
+
+public enum AuthenticationMechanism {
+ /**
+ * The user used password to authenticate to the LocalVC
+ */
+ PASSWORD,
+ /**
+ * The user used the participation+user token to authenticate to the LocalVC
+ */
+ PARTICIPATION_VCS_ACCESS_TOKEN,
+ /**
+ * The user used the user token to authenticate to the LocalVC
+ */
+ USER_VCS_ACCESS_TOKEN,
+ /**
+ * The user used SSH user token to authenticate to the LocalVC
+ */
+ SSH
+}
diff --git a/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/VcsAccessLog.java b/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/VcsAccessLog.java
new file mode 100644
index 000000000000..685cd4df0286
--- /dev/null
+++ b/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/VcsAccessLog.java
@@ -0,0 +1,71 @@
+package de.tum.in.www1.artemis.domain.vcstokens;
+
+import java.time.ZonedDateTime;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.EnumType;
+import jakarta.persistence.Enumerated;
+import jakarta.persistence.ManyToOne;
+import jakarta.persistence.Table;
+
+import org.hibernate.annotations.Cache;
+import org.hibernate.annotations.CacheConcurrencyStrategy;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+import de.tum.in.www1.artemis.domain.DomainObject;
+import de.tum.in.www1.artemis.domain.User;
+import de.tum.in.www1.artemis.domain.participation.Participation;
+import de.tum.in.www1.artemis.web.rest.repository.RepositoryActionType;
+
+/**
+ * A Vcs access log entry.
+ */
+@Entity
+@Table(name = "vcs_access_log")
+@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
+public class VcsAccessLog extends DomainObject {
+
+ @ManyToOne
+ private User user;
+
+ @ManyToOne
+ private Participation participation;
+
+ @Column(name = "name")
+ private String name;
+
+ @Column(name = "email")
+ private String email;
+
+ @Column(name = "repository_action_type", nullable = false)
+ @Enumerated(EnumType.ORDINAL)
+ private RepositoryActionType repositoryActionType;
+
+ @Column(name = "authentication_mechanism", nullable = false)
+ @Enumerated(EnumType.ORDINAL)
+ private AuthenticationMechanism authenticationMechanism;
+
+ @Column(name = "ipAddress")
+ private String ipAddress;
+
+ @Column(name = "timestamp")
+ private ZonedDateTime timestamp;
+
+ public VcsAccessLog(User user, Participation participation, String name, String email, RepositoryActionType repositoryActionType,
+ AuthenticationMechanism authenticationMechanism, String ipAddress) {
+ this.user = user;
+ this.participation = participation;
+ this.name = name;
+ this.email = email;
+ this.repositoryActionType = repositoryActionType;
+ this.authenticationMechanism = authenticationMechanism;
+ this.ipAddress = ipAddress;
+ this.timestamp = ZonedDateTime.now();
+ }
+
+ public VcsAccessLog() {
+ }
+}
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
index da2b5bf78df7..1ce68dd64d8a 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
@@ -5,7 +5,7 @@
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Repository;
-import de.tum.in.www1.artemis.domain.VcsAccessLog;
+import de.tum.in.www1.artemis.domain.vcstokens.VcsAccessLog;
import de.tum.in.www1.artemis.repository.base.ArtemisJpaRepository;
/**
diff --git a/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java b/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
index fd8afa016b3e..c6c2e6caae65 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
@@ -2,18 +2,18 @@
import static de.tum.in.www1.artemis.config.Constants.PROFILE_LOCALVC;
-import jakarta.servlet.http.HttpServletRequest;
-
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import de.tum.in.www1.artemis.domain.User;
-import de.tum.in.www1.artemis.domain.VcsAccessLog;
import de.tum.in.www1.artemis.domain.participation.Participation;
import de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseParticipation;
+import de.tum.in.www1.artemis.domain.vcstokens.AuthenticationMechanism;
+import de.tum.in.www1.artemis.domain.vcstokens.VcsAccessLog;
import de.tum.in.www1.artemis.repository.VcsAccessLogRepository;
+import de.tum.in.www1.artemis.web.rest.repository.RepositoryActionType;
@Profile(PROFILE_LOCALVC)
@Service
@@ -27,10 +27,19 @@ public class VcsAccessLogService {
this.vcsAccessLogRepository = vcsAccessLogRepository;
}
- public void storeOperation(User user, ProgrammingExerciseParticipation participation, String requestURI, String authenticationMechanism, HttpServletRequest request) {
+ /**
+ * Creates a vcs access log entry and stores it to the database
+ *
+ * @param user The user accessing the repository
+ * @param participation The participation which owns the repository
+ * @param actionType The action type: READ or WRITE
+ * @param authenticationMechanism The used authentication mechanism: password, token or SSH
+ */
+ public void storeAccessLog(User user, ProgrammingExerciseParticipation participation, RepositoryActionType actionType, AuthenticationMechanism authenticationMechanism) {
log.debug("Storing access operation for user {}", user);
- VcsAccessLog accessLogEntry = new VcsAccessLog(user, (Participation) participation, "op", "mec", "ip");
+ VcsAccessLog accessLogEntry = new VcsAccessLog(user, (Participation) participation, user.getName(), user.getEmail(), actionType, authenticationMechanism,
+ "not sure how i ge tip yet");
vcsAccessLogRepository.save(accessLogEntry);
}
}
diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java
index 370f23714f2d..bd6dafff842a 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java
@@ -39,6 +39,7 @@
import de.tum.in.www1.artemis.domain.enumeration.RepositoryType;
import de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseParticipation;
import de.tum.in.www1.artemis.domain.participation.SolutionProgrammingExerciseParticipation;
+import de.tum.in.www1.artemis.domain.vcstokens.AuthenticationMechanism;
import de.tum.in.www1.artemis.exception.ContinuousIntegrationException;
import de.tum.in.www1.artemis.exception.VersionControlException;
import de.tum.in.www1.artemis.exception.localvc.LocalVCAuthException;
@@ -208,7 +209,6 @@ public void authenticateAndAuthorizeGitRequest(HttpServletRequest request, Repos
return;
}
}
- String authenticationMechanism = ""; // todo get actual authentication mechanism here: Password / Participation Token / User token
User user = authenticateUser(authorizationHeader);
// Optimization.
@@ -232,17 +232,37 @@ public void authenticateAndAuthorizeGitRequest(HttpServletRequest request, Repos
throw new LocalVCForbiddenException();
}
- authorizeUser(repositoryTypeOrUserName, user, exercise, repositoryAction, localVCRepositoryUri.isPracticeRepository());
+ var authenticationMechanism = resolveAuthenticationMechanism(authorizationHeader, user);
- request.setAttribute("user", user);
+ authorizeUser(repositoryTypeOrUserName, user, exercise, repositoryAction, authenticationMechanism, localVCRepositoryUri.isPracticeRepository());
- // Storing the access to the repository in the VCS access log
- ProgrammingExerciseParticipation participation = null; // Todo get actual participation
- vcsAccessLogService.storeOperation(user, participation, request.getRequestURI(), authenticationMechanism, request);
+ request.setAttribute("user", user);
log.debug("Authorizing user {} for repository {} took {}", user.getLogin(), localVCRepositoryUri, TimeLogUtil.formatDurationFrom(timeNanoStart));
}
+ /**
+ * Resolves the user's authentication mechanism for the repository
+ *
+ * @param authorizationHeader the request's authorizationHeader, containing the token or password
+ * @param user the user
+ * @return the authentication type
+ * @throws LocalVCAuthException if extracting the token or password from the authorizationHeader fails
+ */
+ private AuthenticationMechanism resolveAuthenticationMechanism(String authorizationHeader, User user) throws LocalVCAuthException {
+ UsernameAndPassword usernameAndPassword = extractUsernameAndPassword(authorizationHeader);
+
+ String password = usernameAndPassword.password();
+ if (!password.startsWith("vcpat")) {
+ return AuthenticationMechanism.PASSWORD;
+ }
+ if (password.equals(user.getVcsAccessToken())) {
+ return AuthenticationMechanism.USER_VCS_ACCESS_TOKEN;
+ }
+ return AuthenticationMechanism.PARTICIPATION_VCS_ACCESS_TOKEN;
+
+ }
+
private User authenticateUser(String authorizationHeader) throws LocalVCAuthException {
UsernameAndPassword usernameAndPassword = extractUsernameAndPassword(authorizationHeader);
@@ -342,17 +362,18 @@ private UsernameAndPassword extractUsernameAndPassword(String authorizationHeade
}
/**
- * Authorize a user to access a certain repository.
+ * Authorize a user to access a certain repository, and logs the repository action.
*
* @param repositoryTypeOrUserName The type of the repository or the username of the user.
* @param user The user that wants to access the repository.
* @param exercise The exercise the repository belongs to.
* @param repositoryActionType The type of the action the user wants to perform.
+ * @param authenticationMechanism The authentication mechanism used (password, token or SSH)
* @param isPracticeRepository Whether the repository is a practice repository.
* @throws LocalVCForbiddenException If the user is not allowed to access the repository.
*/
- public void authorizeUser(String repositoryTypeOrUserName, User user, ProgrammingExercise exercise, RepositoryActionType repositoryActionType, boolean isPracticeRepository)
- throws LocalVCForbiddenException {
+ public void authorizeUser(String repositoryTypeOrUserName, User user, ProgrammingExercise exercise, RepositoryActionType repositoryActionType,
+ AuthenticationMechanism authenticationMechanism, boolean isPracticeRepository) throws LocalVCForbiddenException {
if (repositoryTypeOrUserName.equals(RepositoryType.TESTS.toString()) || auxiliaryRepositoryService.isAuxiliaryRepositoryOfExercise(repositoryTypeOrUserName, exercise)) {
// Test and auxiliary repositories are only accessible by instructors and higher.
@@ -374,6 +395,8 @@ public void authorizeUser(String repositoryTypeOrUserName, User user, Programmin
"No participation found for repository with repository type or username " + repositoryTypeOrUserName + " in exercise " + exercise.getId(), e);
}
+ vcsAccessLogService.storeAccessLog(user, participation, repositoryActionType, authenticationMechanism);
+
try {
repositoryAccessService.checkAccessRepositoryElseThrow(participation, user, exercise, repositoryActionType);
}
diff --git a/src/main/java/de/tum/in/www1/artemis/service/icl/SshGitLocationResolverService.java b/src/main/java/de/tum/in/www1/artemis/service/icl/SshGitLocationResolverService.java
index 886c509792f5..e16912b7e511 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/icl/SshGitLocationResolverService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/icl/SshGitLocationResolverService.java
@@ -20,6 +20,7 @@
import de.tum.in.www1.artemis.config.icl.ssh.SshConstants;
import de.tum.in.www1.artemis.domain.ProgrammingExercise;
+import de.tum.in.www1.artemis.domain.vcstokens.AuthenticationMechanism;
import de.tum.in.www1.artemis.exception.localvc.LocalVCForbiddenException;
import de.tum.in.www1.artemis.exception.localvc.LocalVCInternalException;
import de.tum.in.www1.artemis.repository.ProgrammingExerciseRepository;
@@ -78,7 +79,8 @@ public Path resolveRootDirectory(String command, String[] args, ServerSession se
else {
final var user = session.getAttribute(SshConstants.USER_KEY);
try {
- localVCServletService.authorizeUser(repositoryTypeOrUserName, user, exercise, repositoryAction, localVCRepositoryUri.isPracticeRepository());
+ localVCServletService.authorizeUser(repositoryTypeOrUserName, user, exercise, repositoryAction, AuthenticationMechanism.SSH,
+ localVCRepositoryUri.isPracticeRepository());
}
catch (LocalVCForbiddenException e) {
log.error("User {} does not have access to the repository {}", user.getLogin(), repositoryPath);
diff --git a/src/main/resources/config/liquibase/changelog/20240804144500_changelog.xml b/src/main/resources/config/liquibase/changelog/20240804144500_changelog.xml
index 10d82d986557..fcbd69e86f0e 100644
--- a/src/main/resources/config/liquibase/changelog/20240804144500_changelog.xml
+++ b/src/main/resources/config/liquibase/changelog/20240804144500_changelog.xml
@@ -4,11 +4,11 @@
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
-
+
@@ -16,10 +16,16 @@
-
+
-
+
+
+
+
+
+
+
@@ -29,16 +35,6 @@
-
-
From 6d9ba87ed068c21a671e76d3d82205e6e1b0f9d2 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Wed, 7 Aug 2024 11:14:54 +0200
Subject: [PATCH 03/50] added commit_hash to table, similar to bitbucket git
push log
---
.../in/www1/artemis/domain/vcstokens/VcsAccessLog.java | 8 ++++++--
.../tum/in/www1/artemis/service/VcsAccessLogService.java | 2 +-
.../liquibase/changelog/20240804144500_changelog.xml | 3 +++
3 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/VcsAccessLog.java b/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/VcsAccessLog.java
index 685cd4df0286..cc0703c91df3 100644
--- a/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/VcsAccessLog.java
+++ b/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/VcsAccessLog.java
@@ -48,20 +48,24 @@ public class VcsAccessLog extends DomainObject {
@Enumerated(EnumType.ORDINAL)
private AuthenticationMechanism authenticationMechanism;
- @Column(name = "ipAddress")
+ @Column(name = "commit_hash")
+ private String commitHash;
+
+ @Column(name = "ip_address")
private String ipAddress;
@Column(name = "timestamp")
private ZonedDateTime timestamp;
public VcsAccessLog(User user, Participation participation, String name, String email, RepositoryActionType repositoryActionType,
- AuthenticationMechanism authenticationMechanism, String ipAddress) {
+ AuthenticationMechanism authenticationMechanism, String commitHash, String ipAddress) {
this.user = user;
this.participation = participation;
this.name = name;
this.email = email;
this.repositoryActionType = repositoryActionType;
this.authenticationMechanism = authenticationMechanism;
+ this.commitHash = commitHash;
this.ipAddress = ipAddress;
this.timestamp = ZonedDateTime.now();
}
diff --git a/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java b/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
index c6c2e6caae65..df8fb4a9966b 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
@@ -38,7 +38,7 @@ public class VcsAccessLogService {
public void storeAccessLog(User user, ProgrammingExerciseParticipation participation, RepositoryActionType actionType, AuthenticationMechanism authenticationMechanism) {
log.debug("Storing access operation for user {}", user);
- VcsAccessLog accessLogEntry = new VcsAccessLog(user, (Participation) participation, user.getName(), user.getEmail(), actionType, authenticationMechanism,
+ VcsAccessLog accessLogEntry = new VcsAccessLog(user, (Participation) participation, user.getName(), user.getEmail(), actionType, authenticationMechanism, null,
"not sure how i ge tip yet");
vcsAccessLogRepository.save(accessLogEntry);
}
diff --git a/src/main/resources/config/liquibase/changelog/20240804144500_changelog.xml b/src/main/resources/config/liquibase/changelog/20240804144500_changelog.xml
index fcbd69e86f0e..14e939c61d64 100644
--- a/src/main/resources/config/liquibase/changelog/20240804144500_changelog.xml
+++ b/src/main/resources/config/liquibase/changelog/20240804144500_changelog.xml
@@ -28,6 +28,9 @@
+
+
+
From 6c3c048167d50e042ccb4d08634bcfd8d451b569 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Thu, 15 Aug 2024 08:15:35 +0200
Subject: [PATCH 04/50] fixed remaining conflicts
---
.../artemis/service/VcsAccessLogService.java | 6 +++---
.../localvc/LocalVCServletService.java | 20 +++++++++----------
.../icl/SshGitLocationResolverService.java | 2 +-
.../resources/config/liquibase/master.xml | 2 +-
4 files changed, 14 insertions(+), 16 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java b/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
index df8fb4a9966b..c508494574e1 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
@@ -35,11 +35,11 @@ public class VcsAccessLogService {
* @param actionType The action type: READ or WRITE
* @param authenticationMechanism The used authentication mechanism: password, token or SSH
*/
- public void storeAccessLog(User user, ProgrammingExerciseParticipation participation, RepositoryActionType actionType, AuthenticationMechanism authenticationMechanism) {
+ public void storeAccessLog(User user, ProgrammingExerciseParticipation participation, RepositoryActionType actionType, AuthenticationMechanism authenticationMechanism,
+ String ipAddress) {
log.debug("Storing access operation for user {}", user);
- VcsAccessLog accessLogEntry = new VcsAccessLog(user, (Participation) participation, user.getName(), user.getEmail(), actionType, authenticationMechanism, null,
- "not sure how i ge tip yet");
+ VcsAccessLog accessLogEntry = new VcsAccessLog(user, (Participation) participation, user.getName(), user.getEmail(), actionType, authenticationMechanism, null, ipAddress);
vcsAccessLogRepository.save(accessLogEntry);
}
}
diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java
index ecb92c7ab235..eb64a1bd1d18 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java
@@ -148,8 +148,9 @@ public LocalVCServletService(AuthenticationManager authenticationManager, UserRe
this.programmingSubmissionService = programmingSubmissionService;
this.programmingMessagingService = programmingMessagingService;
this.programmingTriggerService = programmingTriggerService;
- this.vcsAccessLogService = vcsAccessLogService;
this.participationVCSAccessTokenRepository = participationVCSAccessTokenRepository;
+ this.vcsAccessLogService = vcsAccessLogService;
+
}
/**
@@ -220,7 +221,6 @@ public void authenticateAndAuthorizeGitRequest(HttpServletRequest request, Repos
return;
}
}
- User user = authenticateUser(authorizationHeader);
// Optimization.
// For each git command (i.e. 'git fetch' or 'git push'), the git client sends three requests.
@@ -245,8 +245,8 @@ public void authenticateAndAuthorizeGitRequest(HttpServletRequest request, Repos
}
var authenticationMechanism = resolveAuthenticationMechanism(authorizationHeader, user);
-
- authorizeUser(repositoryTypeOrUserName, user, exercise, repositoryAction, authenticationMechanism, localVCRepositoryUri.isPracticeRepository());
+ var ipAddress = request.getRemoteAddr();
+ authorizeUser(repositoryTypeOrUserName, user, exercise, repositoryAction, authenticationMechanism, ipAddress, localVCRepositoryUri.isPracticeRepository());
request.setAttribute("user", user);
@@ -275,7 +275,7 @@ private AuthenticationMechanism resolveAuthenticationMechanism(String authorizat
}
- private User authenticateUser(String authorizationHeader) throws LocalVCAuthException {
+ private User authenticateUser(String authorizationHeader, ProgrammingExercise exercise, LocalVCRepositoryUri localVCRepositoryUri) throws LocalVCAuthException {
UsernameAndPassword usernameAndPassword = extractUsernameAndPassword(authorizationHeader);
@@ -374,7 +374,7 @@ private ProgrammingExercise getProgrammingExerciseOrThrow(String projectKey) {
private String checkAuthorizationHeader(String authorizationHeader) throws LocalVCAuthException {
if (authorizationHeader == null) {
- throw new LocalVCAuthException();
+ throw new LocalVCAuthException("No authorization header provided");
}
String[] basicAuthCredentialsEncoded = authorizationHeader.split(" ");
@@ -401,18 +401,17 @@ private UsernameAndPassword extractUsernameAndPassword(String authorizationHeade
}
/**
- * Authorize a user to access a certain repository, and logs the repository action.
+ * Authorize a user to access a certain repository.
*
* @param repositoryTypeOrUserName The type of the repository or the username of the user.
* @param user The user that wants to access the repository.
* @param exercise The exercise the repository belongs to.
* @param repositoryActionType The type of the action the user wants to perform.
- * @param authenticationMechanism The authentication mechanism used (password, token or SSH)
* @param isPracticeRepository Whether the repository is a practice repository.
* @throws LocalVCForbiddenException If the user is not allowed to access the repository.
*/
public void authorizeUser(String repositoryTypeOrUserName, User user, ProgrammingExercise exercise, RepositoryActionType repositoryActionType,
- AuthenticationMechanism authenticationMechanism, boolean isPracticeRepository) throws LocalVCForbiddenException {
+ AuthenticationMechanism authenticationMechanism, String ipAddress, boolean isPracticeRepository) throws LocalVCForbiddenException {
if (repositoryTypeOrUserName.equals(RepositoryType.TESTS.toString()) || auxiliaryRepositoryService.isAuxiliaryRepositoryOfExercise(repositoryTypeOrUserName, exercise)) {
// Test and auxiliary repositories are only accessible by instructors and higher.
@@ -434,10 +433,9 @@ public void authorizeUser(String repositoryTypeOrUserName, User user, Programmin
"No participation found for repository with repository type or username " + repositoryTypeOrUserName + " in exercise " + exercise.getId(), e);
}
- vcsAccessLogService.storeAccessLog(user, participation, repositoryActionType, authenticationMechanism);
-
try {
repositoryAccessService.checkAccessRepositoryElseThrow(participation, user, exercise, repositoryActionType);
+ vcsAccessLogService.storeAccessLog(user, participation, repositoryActionType, authenticationMechanism, ipAddress);
}
catch (AccessForbiddenException e) {
throw new LocalVCForbiddenException(e);
diff --git a/src/main/java/de/tum/in/www1/artemis/service/icl/SshGitLocationResolverService.java b/src/main/java/de/tum/in/www1/artemis/service/icl/SshGitLocationResolverService.java
index e16912b7e511..06964dda6c04 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/icl/SshGitLocationResolverService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/icl/SshGitLocationResolverService.java
@@ -79,7 +79,7 @@ public Path resolveRootDirectory(String command, String[] args, ServerSession se
else {
final var user = session.getAttribute(SshConstants.USER_KEY);
try {
- localVCServletService.authorizeUser(repositoryTypeOrUserName, user, exercise, repositoryAction, AuthenticationMechanism.SSH,
+ localVCServletService.authorizeUser(repositoryTypeOrUserName, user, exercise, repositoryAction, AuthenticationMechanism.SSH, session.getClientAddress().toString(),
localVCRepositoryUri.isPracticeRepository());
}
catch (LocalVCForbiddenException e) {
diff --git a/src/main/resources/config/liquibase/master.xml b/src/main/resources/config/liquibase/master.xml
index 29db6b89931d..d996d0c644d4 100644
--- a/src/main/resources/config/liquibase/master.xml
+++ b/src/main/resources/config/liquibase/master.xml
@@ -20,9 +20,9 @@
+
-
From ea9e1e8b534d5cdcf758a64ae071fce58e0f0c95 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Tue, 20 Aug 2024 11:29:30 +0200
Subject: [PATCH 05/50] add commithash to log
---
.../domain/vcstokens/VcsAccessLog.java | 4 +++
.../repository/VcsAccessLogRepository.java | 18 +++++++++++++
.../artemis/service/VcsAccessLogService.java | 20 ++++++++++++---
.../localvc/LocalVCServletService.java | 25 +++++++++++++++----
.../icl/SshGitLocationResolverService.java | 2 +-
5 files changed, 60 insertions(+), 9 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/VcsAccessLog.java b/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/VcsAccessLog.java
index cc0703c91df3..58b065c957a5 100644
--- a/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/VcsAccessLog.java
+++ b/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/VcsAccessLog.java
@@ -72,4 +72,8 @@ public VcsAccessLog(User user, Participation participation, String name, String
public VcsAccessLog() {
}
+
+ public void setCommitHash(String commitHash) {
+ this.commitHash = commitHash;
+ }
}
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
index 1ce68dd64d8a..17951bd74d0c 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
@@ -2,7 +2,11 @@
import static de.tum.in.www1.artemis.config.Constants.PROFILE_CORE;
+import java.util.Optional;
+
import org.springframework.context.annotation.Profile;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import de.tum.in.www1.artemis.domain.vcstokens.VcsAccessLog;
@@ -20,4 +24,18 @@
@Repository
public interface VcsAccessLogRepository extends ArtemisJpaRepository {
+ /**
+ * Find the access log entry which does not have any commit hash yet
+ *
+ * @param participationId Current time
+ * @return a log entry belonging with the participationId, which has no commit hash
+ */
+ @Query("""
+ SELECT vcsAccessLog
+ FROM VcsAccessLog vcsAccessLog
+ WHERE vcsAccessLog.participation.id = :participationId
+ AND vcsAccessLog.commitHash IS NULL
+ """)
+ Optional findByParticipationIdWhereCommitHashIsNull(@Param("participationId") long participationId);
+
}
diff --git a/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java b/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
index c508494574e1..d354c6f07a70 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
@@ -33,13 +33,27 @@ public class VcsAccessLogService {
* @param user The user accessing the repository
* @param participation The participation which owns the repository
* @param actionType The action type: READ or WRITE
- * @param authenticationMechanism The used authentication mechanism: password, token or SSH
+ * @param authenticationMechanism The used authentication mechanism: password, vcs token (user/participation) or SSH
*/
public void storeAccessLog(User user, ProgrammingExerciseParticipation participation, RepositoryActionType actionType, AuthenticationMechanism authenticationMechanism,
- String ipAddress) {
+ String commitHash, String ipAddress) {
log.debug("Storing access operation for user {}", user);
- VcsAccessLog accessLogEntry = new VcsAccessLog(user, (Participation) participation, user.getName(), user.getEmail(), actionType, authenticationMechanism, null, ipAddress);
+ VcsAccessLog accessLogEntry = new VcsAccessLog(user, (Participation) participation, user.getName(), user.getEmail(), actionType, authenticationMechanism, commitHash,
+ ipAddress);
vcsAccessLogRepository.save(accessLogEntry);
}
+
+ /**
+ * Updates the commit hash after a successful push
+ *
+ * @param participation The participation to which the repository belongs to
+ * @param commitHash The newest commit hash which should get set for the access log entry
+ */
+ public void updateCommitHash(ProgrammingExerciseParticipation participation, String commitHash) {
+ vcsAccessLogRepository.findByParticipationIdWhereCommitHashIsNull(participation.getId()).ifPresent(entry -> {
+ entry.setCommitHash(commitHash);
+ vcsAccessLogRepository.save(entry);
+ });
+ }
}
diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java
index eb64a1bd1d18..8f196143af36 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java
@@ -246,7 +246,7 @@ public void authenticateAndAuthorizeGitRequest(HttpServletRequest request, Repos
var authenticationMechanism = resolveAuthenticationMechanism(authorizationHeader, user);
var ipAddress = request.getRemoteAddr();
- authorizeUser(repositoryTypeOrUserName, user, exercise, repositoryAction, authenticationMechanism, ipAddress, localVCRepositoryUri.isPracticeRepository());
+ authorizeUser(repositoryTypeOrUserName, user, exercise, repositoryAction, authenticationMechanism, ipAddress, localVCRepositoryUri);
request.setAttribute("user", user);
@@ -407,11 +407,11 @@ private UsernameAndPassword extractUsernameAndPassword(String authorizationHeade
* @param user The user that wants to access the repository.
* @param exercise The exercise the repository belongs to.
* @param repositoryActionType The type of the action the user wants to perform.
- * @param isPracticeRepository Whether the repository is a practice repository.
+ * @param localVCRepositoryUri The URI of the local repository.
* @throws LocalVCForbiddenException If the user is not allowed to access the repository.
*/
public void authorizeUser(String repositoryTypeOrUserName, User user, ProgrammingExercise exercise, RepositoryActionType repositoryActionType,
- AuthenticationMechanism authenticationMechanism, String ipAddress, boolean isPracticeRepository) throws LocalVCForbiddenException {
+ AuthenticationMechanism authenticationMechanism, String ipAddress, LocalVCRepositoryUri localVCRepositoryUri) throws LocalVCForbiddenException {
if (repositoryTypeOrUserName.equals(RepositoryType.TESTS.toString()) || auxiliaryRepositoryService.isAuxiliaryRepositoryOfExercise(repositoryTypeOrUserName, exercise)) {
// Test and auxiliary repositories are only accessible by instructors and higher.
@@ -426,7 +426,8 @@ public void authorizeUser(String repositoryTypeOrUserName, User user, Programmin
ProgrammingExerciseParticipation participation;
try {
- participation = programmingExerciseParticipationService.getParticipationForRepository(exercise, repositoryTypeOrUserName, isPracticeRepository, false);
+ participation = programmingExerciseParticipationService.getParticipationForRepository(exercise, repositoryTypeOrUserName, localVCRepositoryUri.isPracticeRepository(),
+ false);
}
catch (EntityNotFoundException e) {
throw new LocalVCInternalException(
@@ -435,11 +436,22 @@ public void authorizeUser(String repositoryTypeOrUserName, User user, Programmin
try {
repositoryAccessService.checkAccessRepositoryElseThrow(participation, user, exercise, repositoryActionType);
- vcsAccessLogService.storeAccessLog(user, participation, repositoryActionType, authenticationMechanism, ipAddress);
}
catch (AccessForbiddenException e) {
throw new LocalVCForbiddenException(e);
}
+ String commitHash = null;
+ try {
+ if (repositoryActionType == RepositoryActionType.READ) {
+ commitHash = getLatestCommitHash(repositories.get(localVCRepositoryUri.getRelativeRepositoryPath().toString()));
+ }
+ }
+ catch (GitAPIException e) {
+ log.warn("Failed to obtain commit hash for repository {}. Error: {}", localVCRepositoryUri.getRelativeRepositoryPath().toString(), e.getMessage());
+ }
+ // Write a access log entry to the database
+ vcsAccessLogService.storeAccessLog(user, participation, repositoryActionType, authenticationMechanism, commitHash, ipAddress);
+
}
/**
@@ -506,6 +518,9 @@ public void processNewPush(String commitHash, Repository repository) {
// Process push to any repository other than the test repository.
processNewPushToRepository(participation, commit);
+
+ // For push the correct commitHash is only available here, therefore the preliminary null value is overwritten
+ vcsAccessLogService.updateCommitHash(participation, commitHash);
}
catch (GitAPIException | IOException e) {
// This catch clause does not catch exceptions that happen during runBuildJob() as that method is called asynchronously.
diff --git a/src/main/java/de/tum/in/www1/artemis/service/icl/SshGitLocationResolverService.java b/src/main/java/de/tum/in/www1/artemis/service/icl/SshGitLocationResolverService.java
index 06964dda6c04..36959d8c3fe7 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/icl/SshGitLocationResolverService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/icl/SshGitLocationResolverService.java
@@ -80,7 +80,7 @@ public Path resolveRootDirectory(String command, String[] args, ServerSession se
final var user = session.getAttribute(SshConstants.USER_KEY);
try {
localVCServletService.authorizeUser(repositoryTypeOrUserName, user, exercise, repositoryAction, AuthenticationMechanism.SSH, session.getClientAddress().toString(),
- localVCRepositoryUri.isPracticeRepository());
+ localVCRepositoryUri);
}
catch (LocalVCForbiddenException e) {
log.error("User {} does not have access to the repository {}", user.getLogin(), repositoryPath);
From 0e39d582f59b5cbc0ff86531d392a351ca0aed05 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Sun, 25 Aug 2024 07:09:42 +0200
Subject: [PATCH 06/50] code editor submit logging
---
.../vcstokens/AuthenticationMechanism.java | 6 ++++-
.../artemis/service/VcsAccessLogService.java | 27 ++++++++++++++++++-
.../programming/RepositoryService.java | 9 +++++--
.../rest/repository/RepositoryResource.java | 2 +-
4 files changed, 39 insertions(+), 5 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/AuthenticationMechanism.java b/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/AuthenticationMechanism.java
index 815964e49ae3..108f46e646e5 100644
--- a/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/AuthenticationMechanism.java
+++ b/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/AuthenticationMechanism.java
@@ -16,5 +16,9 @@ public enum AuthenticationMechanism {
/**
* The user used SSH user token to authenticate to the LocalVC
*/
- SSH
+ SSH,
+ /**
+ * The user used the artemis client code editor to authenticate to the LocalVC
+ */
+ CODE_EDITOR
}
diff --git a/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java b/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
index d354c6f07a70..e1ee6b79cacf 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
@@ -2,16 +2,20 @@
import static de.tum.in.www1.artemis.config.Constants.PROFILE_LOCALVC;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
+import de.tum.in.www1.artemis.domain.Repository;
import de.tum.in.www1.artemis.domain.User;
import de.tum.in.www1.artemis.domain.participation.Participation;
import de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseParticipation;
import de.tum.in.www1.artemis.domain.vcstokens.AuthenticationMechanism;
import de.tum.in.www1.artemis.domain.vcstokens.VcsAccessLog;
+import de.tum.in.www1.artemis.repository.ParticipationRepository;
import de.tum.in.www1.artemis.repository.VcsAccessLogRepository;
import de.tum.in.www1.artemis.web.rest.repository.RepositoryActionType;
@@ -23,8 +27,11 @@ public class VcsAccessLogService {
private final VcsAccessLogRepository vcsAccessLogRepository;
- VcsAccessLogService(VcsAccessLogRepository vcsAccessLogRepository) {
+ private final ParticipationRepository participationRepository;
+
+ VcsAccessLogService(VcsAccessLogRepository vcsAccessLogRepository, ParticipationRepository participationRepository) {
this.vcsAccessLogRepository = vcsAccessLogRepository;
+ this.participationRepository = participationRepository;
}
/**
@@ -56,4 +63,22 @@ public void updateCommitHash(ProgrammingExerciseParticipation participation, Str
vcsAccessLogRepository.save(entry);
});
}
+
+ /**
+ * Stores the log for a push from the code editor.
+ *
+ * @param repo The repository to which the push is executed
+ * @param user The user submitting the change
+ * @param participationId The id of the participation belonging to the repository
+ * @throws GitAPIException if an error occurs while retrieving the git log
+ */
+ public void storeCodeEditorAccessLog(Repository repo, User user, Long participationId) throws GitAPIException {
+ try (Git git = new Git(repo)) {
+ String lastCommitHash = git.log().setMaxCount(1).call().iterator().next().getName();
+ var participation = participationRepository.findById(participationId);
+ if (participation.isPresent() && participation.get() instanceof ProgrammingExerciseParticipation programmingParticipation) {
+ storeAccessLog(user, programmingParticipation, RepositoryActionType.WRITE, AuthenticationMechanism.CODE_EDITOR, lastCommitHash, null);
+ }
+ }
+ }
}
diff --git a/src/main/java/de/tum/in/www1/artemis/service/programming/RepositoryService.java b/src/main/java/de/tum/in/www1/artemis/service/programming/RepositoryService.java
index fd88f0a10e03..6470c32d3885 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/programming/RepositoryService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/programming/RepositoryService.java
@@ -45,6 +45,7 @@
import de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseParticipation;
import de.tum.in.www1.artemis.service.FileService;
import de.tum.in.www1.artemis.service.ProfileService;
+import de.tum.in.www1.artemis.service.VcsAccessLogService;
import de.tum.in.www1.artemis.service.connectors.GitService;
import de.tum.in.www1.artemis.web.rest.dto.FileMove;
import de.tum.in.www1.artemis.web.rest.errors.ConflictException;
@@ -60,11 +61,14 @@ public class RepositoryService {
private final ProfileService profileService;
+ private final VcsAccessLogService vcsAccessLogService;
+
private static final Logger log = LoggerFactory.getLogger(RepositoryService.class);
- public RepositoryService(GitService gitService, ProfileService profileService) {
+ public RepositoryService(GitService gitService, ProfileService profileService, VcsAccessLogService vcsAccessLogService) {
this.gitService = gitService;
this.profileService = profileService;
+ this.vcsAccessLogService = vcsAccessLogService;
}
/**
@@ -471,9 +475,10 @@ public void pullChanges(Repository repository) {
* @param user the user who has committed the changes in the online editor
* @throws GitAPIException if the staging/committing process fails.
*/
- public void commitChanges(Repository repository, User user) throws GitAPIException {
+ public void commitChanges(Repository repository, User user, Long domainId) throws GitAPIException {
gitService.stageAllChanges(repository);
gitService.commitAndPush(repository, "Changes by Online Editor", true, user);
+ vcsAccessLogService.storeCodeEditorAccessLog(repository, user, domainId);
}
/**
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/repository/RepositoryResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/repository/RepositoryResource.java
index e7cab2a54b20..d1378d7c03ad 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/repository/RepositoryResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/repository/RepositoryResource.java
@@ -281,7 +281,7 @@ public ResponseEntity commitChanges(Long domainId) {
return executeAndCheckForExceptions(() -> {
Repository repository = getRepository(domainId, RepositoryActionType.WRITE, true);
- repositoryService.commitChanges(repository, user);
+ repositoryService.commitChanges(repository, user, domainId);
// Trigger a build, and process the result. Only implemented for local CI.
// For GitLab + Jenkins, webhooks were added when creating the repository,
// that notify the CI system when the commit happens and thus trigger the build.
From fd2c65a6f4b0d78fad07cec71357f1f0bf6a0a52 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Mon, 26 Aug 2024 11:36:10 +0200
Subject: [PATCH 07/50] added UI
---
.../repository/VcsAccessLogRepository.java | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
index 17951bd74d0c..d517edae73ae 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
@@ -2,6 +2,7 @@
import static de.tum.in.www1.artemis.config.Constants.PROFILE_CORE;
+import java.util.List;
import java.util.Optional;
import org.springframework.context.annotation.Profile;
@@ -27,7 +28,7 @@ public interface VcsAccessLogRepository extends ArtemisJpaRepository findByParticipationIdWhereCommitHashIsNull(@Param("participationId") long participationId);
+ /**
+ * Retrieves a list of {@link VcsAccessLog} entities associated with the specified participation ID.
+ * The results are ordered by the log ID in ascending order.
+ *
+ * @param participationId the ID of the participation to filter the access logs by.
+ * @return a list of {@link VcsAccessLog} entities for the given participation ID, sorted by log ID in ascending order.
+ */
+ @Query("""
+ SELECT vcsAccessLog
+ FROM VcsAccessLog vcsAccessLog
+ WHERE vcsAccessLog.participation.id = :participationId
+ ORDER BY vcsAccessLog.id ASC
+ """)
+ List findAllByParticipationId(long participationId);
}
From 6b91dd2d9895855a1b5c36d735ec7635f75b5fa8 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Mon, 26 Aug 2024 11:36:29 +0200
Subject: [PATCH 08/50] added UI
---
.../domain/vcstokens/VcsAccessLog.java | 28 ++++++
.../artemis/web/rest/dto/VcsAccessLogDTO.java | 29 ++++++
...grammingExerciseParticipationResource.java | 51 ++++++++++-
.../entities/vcs-access-log-entry.model.ts | 13 +++
...ming-exercise-management-routing.module.ts | 25 ++++++
...gramming-exercise-participation.service.ts | 22 +++++
.../repository-view.component.html | 7 ++
.../repository-view.component.ts | 3 +
...-repository-access-log-view.component.html | 34 +++++++
...cs-repository-access-log-view.component.ts | 89 +++++++++++++++++++
src/main/webapp/i18n/de/repository.json | 11 +++
src/main/webapp/i18n/en/repository.json | 11 +++
12 files changed, 322 insertions(+), 1 deletion(-)
create mode 100644 src/main/java/de/tum/in/www1/artemis/web/rest/dto/VcsAccessLogDTO.java
create mode 100644 src/main/webapp/app/entities/vcs-access-log-entry.model.ts
create mode 100644 src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.html
create mode 100644 src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
diff --git a/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/VcsAccessLog.java b/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/VcsAccessLog.java
index 58b065c957a5..284c787d1f94 100644
--- a/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/VcsAccessLog.java
+++ b/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/VcsAccessLog.java
@@ -76,4 +76,32 @@ public VcsAccessLog() {
public void setCommitHash(String commitHash) {
this.commitHash = commitHash;
}
+
+ public User getUser() {
+ return user;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public String getCommitHash() {
+ return commitHash;
+ }
+
+ public ZonedDateTime getTimestamp() {
+ return timestamp;
+ }
+
+ public AuthenticationMechanism getAuthenticationMechanism() {
+ return authenticationMechanism;
+ }
+
+ public RepositoryActionType getRepositoryActionType() {
+ return repositoryActionType;
+ }
}
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/dto/VcsAccessLogDTO.java b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/VcsAccessLogDTO.java
new file mode 100644
index 000000000000..96abf0bd46ef
--- /dev/null
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/dto/VcsAccessLogDTO.java
@@ -0,0 +1,29 @@
+package de.tum.in.www1.artemis.web.rest.dto;
+
+import java.time.ZonedDateTime;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+
+import de.tum.in.www1.artemis.domain.vcstokens.VcsAccessLog;
+
+/**
+ * DTO representing a VCS access log entry.
+ *
+ * @param id The id of the access log entry.
+ * @param userId The user's id associated with the access log event.
+ * @param name The name associated with the user.
+ * @param email The email associated with the user.
+ * @param repositoryActionType The type of action performed in the repository (read or write).
+ * @param authenticationMechanism The method the user used for authenticating to the repository.
+ * @param commitHash The latest commit hash at the access event.
+ * @param timestamp The date and time when the access event occurred.
+ */
+@JsonInclude(JsonInclude.Include.NON_EMPTY)
+public record VcsAccessLogDTO(Long id, Long userId, String name, String email, String repositoryActionType, String authenticationMechanism, String commitHash,
+ ZonedDateTime timestamp) {
+
+ public static VcsAccessLogDTO of(VcsAccessLog vcsAccessLog) {
+ return new VcsAccessLogDTO(vcsAccessLog.getId(), vcsAccessLog.getUser().getId(), vcsAccessLog.getName(), vcsAccessLog.getEmail(),
+ vcsAccessLog.getRepositoryActionType().name(), vcsAccessLog.getAuthenticationMechanism().name(), vcsAccessLog.getCommitHash(), vcsAccessLog.getTimestamp());
+ }
+}
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
index ff820ab4e484..e89be11c9b70 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
@@ -29,11 +29,13 @@
import de.tum.in.www1.artemis.domain.participation.Participation;
import de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseParticipation;
import de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseStudentParticipation;
+import de.tum.in.www1.artemis.domain.vcstokens.VcsAccessLog;
import de.tum.in.www1.artemis.repository.ParticipationRepository;
import de.tum.in.www1.artemis.repository.ProgrammingExerciseRepository;
import de.tum.in.www1.artemis.repository.ProgrammingExerciseStudentParticipationRepository;
import de.tum.in.www1.artemis.repository.ResultRepository;
import de.tum.in.www1.artemis.repository.StudentExamRepository;
+import de.tum.in.www1.artemis.repository.VcsAccessLogRepository;
import de.tum.in.www1.artemis.security.Role;
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastInstructor;
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastStudent;
@@ -46,6 +48,7 @@
import de.tum.in.www1.artemis.service.programming.ProgrammingSubmissionService;
import de.tum.in.www1.artemis.service.programming.RepositoryService;
import de.tum.in.www1.artemis.web.rest.dto.CommitInfoDTO;
+import de.tum.in.www1.artemis.web.rest.dto.VcsAccessLogDTO;
import de.tum.in.www1.artemis.web.rest.errors.AccessForbiddenException;
import de.tum.in.www1.artemis.web.rest.errors.BadRequestAlertException;
import de.tum.in.www1.artemis.web.rest.errors.EntityNotFoundException;
@@ -79,11 +82,13 @@ public class ProgrammingExerciseParticipationResource {
private final StudentExamRepository studentExamRepository;
+ private final VcsAccessLogRepository vcsAccessLogRepository;
+
public ProgrammingExerciseParticipationResource(ProgrammingExerciseParticipationService programmingExerciseParticipationService, ResultRepository resultRepository,
ParticipationRepository participationRepository, ProgrammingExerciseStudentParticipationRepository programmingExerciseStudentParticipationRepository,
ProgrammingSubmissionService submissionService, ProgrammingExerciseRepository programmingExerciseRepository, AuthorizationCheckService authCheckService,
ResultService resultService, ParticipationAuthorizationCheckService participationAuthCheckService, RepositoryService repositoryService,
- StudentExamRepository studentExamRepository) {
+ StudentExamRepository studentExamRepository, VcsAccessLogRepository vcsAccessLogRepository) {
this.programmingExerciseParticipationService = programmingExerciseParticipationService;
this.participationRepository = participationRepository;
this.programmingExerciseStudentParticipationRepository = programmingExerciseStudentParticipationRepository;
@@ -95,6 +100,7 @@ public ProgrammingExerciseParticipationResource(ProgrammingExerciseParticipation
this.participationAuthCheckService = participationAuthCheckService;
this.repositoryService = repositoryService;
this.studentExamRepository = studentExamRepository;
+ this.vcsAccessLogRepository = vcsAccessLogRepository;
}
/**
@@ -305,6 +311,23 @@ public ResponseEntity> getCommitHistoryForParticipationRepo(
return ResponseEntity.ok(commitInfo);
}
+ /**
+ * GET /programming-exercise-participations/{participationId}/vcs-access-log :
+ * Here we check if the user is least an instructor for the exercise. If true the user can have access to the vcs access log of any participation of the exercise.
+ *
+ * @param participationId the id of the participation for which to retrieve the vcs access log
+ * @return the ResponseEntity with status 200 (OK) and with body containing a list of vcsAccessLogDTOs of the participation
+ */
+ @GetMapping("programming-exercise-participations/{participationId}/vcs-access-log")
+ @EnforceAtLeastInstructor
+ public ResponseEntity> getVcsAccessLogForParticipationRepo(@PathVariable long participationId) {
+ ProgrammingExerciseStudentParticipation participation = programmingExerciseStudentParticipationRepository.findByIdElseThrow(participationId);
+ participationAuthCheckService.checkCanAccessParticipationElseThrow(participation);
+ List vcsAccessLogs = vcsAccessLogRepository.findAllByParticipationId(participationId);
+ var vcsAccessLogDTOs = vcsAccessLogs.stream().map(VcsAccessLogDTO::of).toList();
+ return ResponseEntity.ok(vcsAccessLogDTOs);
+ }
+
/**
* GET /programming-exercise/{exerciseID}/commit-history/{repositoryType} : Get the commit history of a programming exercise repository. The repository type can be TEMPLATE or
* SOLUTION or TESTS.
@@ -392,6 +415,32 @@ else if (repositoryType != null) {
}
}
+ /**
+ * Retrieves the VCS access logs for the specified programming exercise's template or solution participation
+ *
+ * @param exerciseId the ID of the programming exercise
+ * @param repositoryType the type of repository (either TEMPLATE or SOLUTION) for which to retrieve the logs.
+ * @return a {@link ResponseEntity} containing a list of {@link VcsAccessLogDTO} objects representing
+ * the VCS access logs.
+ * @throws BadRequestAlertException if the repository type is invalid
+ */
+ @GetMapping("programming-exercise/{exerciseId}/vcs-access-log/{repositoryType}")
+ @EnforceAtLeastStudent
+ public ResponseEntity> getVcsAccessLogForExerciseRepository(@PathVariable long exerciseId, @PathVariable RepositoryType repositoryType) {
+
+ if (repositoryType != RepositoryType.TEMPLATE && repositoryType != RepositoryType.SOLUTION) {
+ throw new BadRequestAlertException("Can only get vcs access log from template and assignment repositories", ENTITY_NAME, "incorrect repositoryType");
+ }
+ ProgrammingExercise programmingExercise = programmingExerciseRepository.findByIdWithTemplateAndSolutionParticipationAndAuxiliaryRepositoriesElseThrow(exerciseId);
+ authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.INSTRUCTOR, programmingExercise, null);
+
+ var participation = repositoryType == RepositoryType.TEMPLATE ? programmingExercise.getTemplateParticipation() : programmingExercise.getSolutionParticipation();
+
+ List vcsAccessLogs = vcsAccessLogRepository.findAllByParticipationId(participation.getId());
+ var vcsAccessLogDTOs = vcsAccessLogs.stream().map(VcsAccessLogDTO::of).toList();
+ return ResponseEntity.ok(vcsAccessLogDTOs);
+ }
+
/**
* Checks if the user has access to the participation.
* If the exercise has not started yet and the user is a student, access is denied.
diff --git a/src/main/webapp/app/entities/vcs-access-log-entry.model.ts b/src/main/webapp/app/entities/vcs-access-log-entry.model.ts
new file mode 100644
index 000000000000..c2571e9ddd0d
--- /dev/null
+++ b/src/main/webapp/app/entities/vcs-access-log-entry.model.ts
@@ -0,0 +1,13 @@
+import { BaseEntity } from 'app/shared/model/base-entity';
+import dayjs from 'dayjs/esm';
+
+export class VcsAccessLogDTO implements BaseEntity {
+ public id?: number;
+ public userId?: number;
+ public name?: string;
+ public email?: string;
+ public repositoryActionType: string;
+ public authenticationMechanism: string;
+ public commitHash?: string;
+ public timestamp: dayjs.Dayjs;
+}
diff --git a/src/main/webapp/app/exercises/programming/manage/programming-exercise-management-routing.module.ts b/src/main/webapp/app/exercises/programming/manage/programming-exercise-management-routing.module.ts
index 39ed8888464a..da8e580e4f62 100644
--- a/src/main/webapp/app/exercises/programming/manage/programming-exercise-management-routing.module.ts
+++ b/src/main/webapp/app/exercises/programming/manage/programming-exercise-management-routing.module.ts
@@ -18,6 +18,7 @@ import { RepositoryViewComponent } from 'app/localvc/repository-view/repository-
import { CommitHistoryComponent } from 'app/localvc/commit-history/commit-history.component';
import { CommitDetailsViewComponent } from 'app/localvc/commit-details-view/commit-details-view.component';
import { LocalVCGuard } from 'app/localvc/localvc-guard.service';
+import { VcsRepositoryAccessLogViewComponent } from 'app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component';
@Injectable({ providedIn: 'root' })
export class ProgrammingExerciseResolve implements Resolve {
@@ -183,6 +184,18 @@ export const routes: Routes = [
},
canActivate: [LocalVCGuard],
},
+ {
+ path: ':courseId/programming-exercises/:exerciseId/repository/:repositoryType/vcs-access-log',
+ component: VcsRepositoryAccessLogViewComponent,
+ data: {
+ authorities: [Authority.ADMIN, Authority.INSTRUCTOR, Authority.EDITOR],
+ pageTitle: 'artemisApp.repository.title',
+ flushRepositoryCacheAfter: 900000, // 15 min
+ participationCache: {},
+ repositoryCache: {},
+ },
+ canActivate: [LocalVCGuard],
+ },
{
path: ':courseId/programming-exercises/:exerciseId/repository/:repositoryType/commit-history/:commitHash',
component: CommitDetailsViewComponent,
@@ -219,6 +232,18 @@ export const routes: Routes = [
},
canActivate: [UserRouteAccessService, LocalVCGuard],
},
+ {
+ path: ':courseId/programming-exercises/:exerciseId/participations/:participationId/repository/vcs-access-log',
+ component: VcsRepositoryAccessLogViewComponent,
+ data: {
+ authorities: [Authority.ADMIN, Authority.INSTRUCTOR],
+ pageTitle: 'artemisApp.repository.title',
+ flushRepositoryCacheAfter: 900000, // 15 min
+ participationCache: {},
+ repositoryCache: {},
+ },
+ canActivate: [UserRouteAccessService, LocalVCGuard],
+ },
{
path: ':courseId/programming-exercises/:exerciseId/participations/:participationId/repository/commit-history/:commitHash',
component: CommitDetailsViewComponent,
diff --git a/src/main/webapp/app/exercises/programming/manage/services/programming-exercise-participation.service.ts b/src/main/webapp/app/exercises/programming/manage/services/programming-exercise-participation.service.ts
index 7af098d1a5a7..01816860ce33 100644
--- a/src/main/webapp/app/exercises/programming/manage/services/programming-exercise-participation.service.ts
+++ b/src/main/webapp/app/exercises/programming/manage/services/programming-exercise-participation.service.ts
@@ -8,6 +8,7 @@ import { EntityTitleService, EntityType } from 'app/shared/layouts/navbar/entity
import { createRequestOption } from 'app/shared/util/request.util';
import { Observable, map, tap } from 'rxjs';
import { CommitInfo } from 'app/entities/programming-submission.model';
+import { VcsAccessLogDTO } from 'app/entities/vcs-access-log-entry.model';
export interface IProgrammingExerciseParticipationService {
getLatestResultWithFeedback: (participationId: number, withSubmission: boolean) => Observable;
@@ -145,6 +146,27 @@ export class ProgrammingExerciseParticipationService implements IProgrammingExer
return this.http.get(`${this.resourceUrlParticipations}${participationId}/commits-info`);
}
+ /**
+ * Get the repository files with content for a given participation id at a specific commit hash.
+ * The current user needs to be at least a instructor in the course of the participation.
+ * @param participationId of the participation to get the commit infos for
+ */
+ getVcsAccessLogForParticipation(participationId: number): Observable {
+ return this.http
+ .get(`${this.resourceUrlParticipations}${participationId}/vcs-access-log`, { observe: 'response' })
+ .pipe(map((res: HttpResponse) => res.body ?? undefined));
+ }
+
+ getVcsAccessLogForExerciseRepository(exerciseId: number, repositoryType: string) {
+ const params: { [key: string]: number | string } = {};
+ if (repositoryType) {
+ params['repositoryType'] = repositoryType;
+ }
+ return this.http
+ .get(`${this.resourceUrl}${exerciseId}/vcs-access-log/${repositoryType}`, { observe: 'response' })
+ .pipe(map((res: HttpResponse) => res.body ?? undefined));
+ }
+
/**
* Get the repository files with content for a given participation id at a specific commit hash.
* The current user needs to be at least a student in the course of the participation.
diff --git a/src/main/webapp/app/localvc/repository-view/repository-view.component.html b/src/main/webapp/app/localvc/repository-view/repository-view.component.html
index b3e821dbf431..84eda91f8370 100644
--- a/src/main/webapp/app/localvc/repository-view/repository-view.component.html
+++ b/src/main/webapp/app/localvc/repository-view/repository-view.component.html
@@ -34,6 +34,13 @@
}
+ @if (routeVcsAccessLog) {
+
+
+
+
+ }
+
@if (exercise?.allowOfflineIde) {
+
+
+
+
+
+
+
+
+ # |
+ |
+ |
+ |
+ |
+ |
+ |
+
+
+
+ @for (entry of vcsAccessLogEntries; track entry; let i = $index) {
+
+ {{ i }} |
+ {{ entry.userId }} |
+ {{ entry.name + ', ' + entry.email }} |
+ {{ entry.repositoryActionType }} |
+ {{ entry.authenticationMechanism }} |
+ {{ entry.commitHash }} |
+ {{ entry.timestamp }} |
+
+ }
+
+
+
+
diff --git a/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts b/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
new file mode 100644
index 000000000000..4704f3c12afa
--- /dev/null
+++ b/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
@@ -0,0 +1,89 @@
+import { Component, OnDestroy, OnInit } from '@angular/core';
+import { Subject, Subscription } from 'rxjs';
+import { ActivatedRoute, Router } from '@angular/router';
+import { DomainService } from 'app/exercises/programming/shared/code-editor/service/code-editor-domain.service';
+import { ProgrammingExercise } from 'app/entities/programming-exercise.model';
+import { ProgrammingExerciseStudentParticipation } from 'app/entities/participation/programming-exercise-student-participation.model';
+import { AccountService } from 'app/core/auth/account.service';
+import { ProgrammingExerciseParticipationService } from 'app/exercises/programming/manage/services/programming-exercise-participation.service';
+import { ProgrammingExerciseService } from 'app/exercises/programming/manage/services/programming-exercise.service';
+import { VcsAccessLogDTO } from 'app/entities/vcs-access-log-entry.model';
+import { HttpErrorResponse } from '@angular/common/http';
+import { AlertService } from 'app/core/util/alert.service';
+import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module';
+
+@Component({
+ selector: 'jhi-vcs-repository-access-log-view',
+ templateUrl: './vcs-repository-access-log-view.component.html',
+ standalone: true,
+ imports: [ArtemisSharedCommonModule],
+})
+export class VcsRepositoryAccessLogViewComponent implements OnInit, OnDestroy {
+ participationId: number;
+ vcsAccessLogEntries: VcsAccessLogDTO[];
+ protected dialogErrorSource = new Subject();
+
+ paramSub: Subscription;
+ routeVcsAccessLog: string;
+ participation: ProgrammingExerciseStudentParticipation;
+ exercise: ProgrammingExercise;
+
+ constructor(
+ private accountService: AccountService,
+ public domainService: DomainService,
+ private route: ActivatedRoute,
+ private programmingExerciseParticipationService: ProgrammingExerciseParticipationService,
+ private programmingExerciseService: ProgrammingExerciseService,
+ private router: Router,
+ private alertService: AlertService,
+ ) {}
+
+ ngOnDestroy(): void {
+ this.participationId = 0;
+ // throw new Error('Method not implemented.');
+ }
+
+ ngOnInit(): void {
+ this.routeVcsAccessLog = this.router.url + '/vcs-access-log';
+ this.paramSub = this.route.params.subscribe((params) => {
+ const participationId = Number(params['participationId']);
+ const exerciseId = Number(params['exerciseId']);
+ const reposiotryType = params['repositoryType'];
+ if (participationId) {
+ this.loadVcsAccessLogForParticipation(participationId);
+ } else {
+ this.loadVcsAccessLog(exerciseId, reposiotryType);
+ }
+ });
+ }
+
+ private loadVcsAccessLogForParticipation(participationId: number) {
+ this.programmingExerciseParticipationService.getVcsAccessLogForParticipation(participationId).subscribe({
+ next: (next: VcsAccessLogDTO[] | undefined) => {
+ if (next) {
+ this.vcsAccessLogEntries = next;
+ this.dialogErrorSource.next('');
+ }
+ },
+ error: (error: HttpErrorResponse) => {
+ this.dialogErrorSource.next(error.message);
+ this.alertService.error('artemisApp.repository.vcsAccessLog.error');
+ },
+ });
+ }
+
+ private loadVcsAccessLog(exerciseId: number, reposiotryType: any) {
+ this.programmingExerciseParticipationService.getVcsAccessLogForExerciseRepository(exerciseId, reposiotryType).subscribe({
+ next: (next: VcsAccessLogDTO[] | undefined) => {
+ if (next) {
+ this.vcsAccessLogEntries = next;
+ this.dialogErrorSource.next('');
+ }
+ },
+ error: (error: HttpErrorResponse) => {
+ this.dialogErrorSource.next(error.message);
+ this.alertService.error('artemisApp.repository.vcsAccessLog.error');
+ },
+ });
+ }
+}
diff --git a/src/main/webapp/i18n/de/repository.json b/src/main/webapp/i18n/de/repository.json
index 241f5d8bc21d..cc8fb238608b 100644
--- a/src/main/webapp/i18n/de/repository.json
+++ b/src/main/webapp/i18n/de/repository.json
@@ -20,6 +20,17 @@
"empty": "Keine Änderungen",
"fileUnchanged": "Keine Änderungen am Dateiinhalt"
}
+ },
+ "vcsAccessLog": {
+ "title": "VCS-Zugriffsprotokoll",
+ "openVcsAccessLog": "Zugriffsprotokoll öffnen",
+ "userId": "Benutzer Id",
+ "author": "Author",
+ "actionType": "Aktionstyp",
+ "authMechanism": "Authentifizierung",
+ "commitHash": "Commit Hash",
+ "timeStamp": "Zeitstempel",
+ "error": "VCS-Zugriffsprotokoll konnte nicht abgerufen werden"
}
}
}
diff --git a/src/main/webapp/i18n/en/repository.json b/src/main/webapp/i18n/en/repository.json
index 59b50fcb0481..4c45f606751a 100644
--- a/src/main/webapp/i18n/en/repository.json
+++ b/src/main/webapp/i18n/en/repository.json
@@ -20,6 +20,17 @@
"empty": "No changes",
"fileUnchanged": "No changes in the file content"
}
+ },
+ "vcsAccessLog": {
+ "title": "VCS Access Log",
+ "openVcsAccessLog": "Open Access Log",
+ "userId": "User Id",
+ "author": "Author",
+ "actionType": "Action Type",
+ "authMechanism": "Authentication",
+ "commitHash": "Commit Hash",
+ "timeStamp": "Timestamp",
+ "error": "VCS Access log could not be retrieved"
}
}
}
From 316dbaaecd82c4f5bc0fb4fbd4c86ce748c35900 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Mon, 26 Aug 2024 12:12:44 +0200
Subject: [PATCH 09/50] remove onDestroy
---
.../vcs-repository-access-log-view.component.ts | 9 ++-------
1 file changed, 2 insertions(+), 7 deletions(-)
diff --git a/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts b/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
index 4704f3c12afa..be801530291f 100644
--- a/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
+++ b/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
@@ -1,4 +1,4 @@
-import { Component, OnDestroy, OnInit } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
import { Subject, Subscription } from 'rxjs';
import { ActivatedRoute, Router } from '@angular/router';
import { DomainService } from 'app/exercises/programming/shared/code-editor/service/code-editor-domain.service';
@@ -18,7 +18,7 @@ import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module';
standalone: true,
imports: [ArtemisSharedCommonModule],
})
-export class VcsRepositoryAccessLogViewComponent implements OnInit, OnDestroy {
+export class VcsRepositoryAccessLogViewComponent implements OnInit {
participationId: number;
vcsAccessLogEntries: VcsAccessLogDTO[];
protected dialogErrorSource = new Subject();
@@ -38,11 +38,6 @@ export class VcsRepositoryAccessLogViewComponent implements OnInit, OnDestroy {
private alertService: AlertService,
) {}
- ngOnDestroy(): void {
- this.participationId = 0;
- // throw new Error('Method not implemented.');
- }
-
ngOnInit(): void {
this.routeVcsAccessLog = this.router.url + '/vcs-access-log';
this.paramSub = this.route.params.subscribe((params) => {
From 5bd2d84a479f6b09c91c24d1b422b3393d0c9b38 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Mon, 26 Aug 2024 12:14:15 +0200
Subject: [PATCH 10/50] remove empty line
---
.../service/connectors/localvc/LocalVCServletService.java | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java
index 8f196143af36..95ff335abf5c 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java
@@ -150,7 +150,6 @@ public LocalVCServletService(AuthenticationManager authenticationManager, UserRe
this.programmingTriggerService = programmingTriggerService;
this.participationVCSAccessTokenRepository = participationVCSAccessTokenRepository;
this.vcsAccessLogService = vcsAccessLogService;
-
}
/**
From 488e04104015ed72795e7814e63e8edcfce46385 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Mon, 26 Aug 2024 17:10:51 +0200
Subject: [PATCH 11/50] fix java docs
---
.../de/tum/in/www1/artemis/service/VcsAccessLogService.java | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java b/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
index e1ee6b79cacf..16c49ab654ce 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
@@ -41,6 +41,8 @@ public class VcsAccessLogService {
* @param participation The participation which owns the repository
* @param actionType The action type: READ or WRITE
* @param authenticationMechanism The used authentication mechanism: password, vcs token (user/participation) or SSH
+ * @param commitHash The latest commit hash
+ * @param ipAddress The ip address of the user accessing the repository
*/
public void storeAccessLog(User user, ProgrammingExerciseParticipation participation, RepositoryActionType actionType, AuthenticationMechanism authenticationMechanism,
String commitHash, String ipAddress) {
From 54238aeee75cbd9a9964088fcf08d5a61fbe88b1 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Mon, 26 Aug 2024 17:28:42 +0200
Subject: [PATCH 12/50] fix java docs and improve client code
---
.../repository/VcsAccessLogRepository.java | 2 +-
...gramming-exercise-participation.service.ts | 2 +-
...cs-repository-access-log-view.component.ts | 35 +++++++------------
3 files changed, 15 insertions(+), 24 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
index d517edae73ae..a6c9b049f448 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
@@ -29,7 +29,7 @@ public interface VcsAccessLogRepository extends ArtemisJpaRepository) => res.body ?? undefined));
}
- getVcsAccessLogForExerciseRepository(exerciseId: number, repositoryType: string) {
+ getVcsAccessLogForRepository(exerciseId: number, repositoryType: string) {
const params: { [key: string]: number | string } = {};
if (repositoryType) {
params['repositoryType'] = repositoryType;
diff --git a/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts b/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
index be801530291f..1c4458b8d58b 100644
--- a/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
+++ b/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
@@ -1,12 +1,10 @@
import { Component, OnInit } from '@angular/core';
-import { Subject, Subscription } from 'rxjs';
+import { Observable, Subject, Subscription } from 'rxjs';
import { ActivatedRoute, Router } from '@angular/router';
import { DomainService } from 'app/exercises/programming/shared/code-editor/service/code-editor-domain.service';
import { ProgrammingExercise } from 'app/entities/programming-exercise.model';
import { ProgrammingExerciseStudentParticipation } from 'app/entities/participation/programming-exercise-student-participation.model';
-import { AccountService } from 'app/core/auth/account.service';
import { ProgrammingExerciseParticipationService } from 'app/exercises/programming/manage/services/programming-exercise-participation.service';
-import { ProgrammingExerciseService } from 'app/exercises/programming/manage/services/programming-exercise.service';
import { VcsAccessLogDTO } from 'app/entities/vcs-access-log-entry.model';
import { HttpErrorResponse } from '@angular/common/http';
import { AlertService } from 'app/core/util/alert.service';
@@ -29,11 +27,9 @@ export class VcsRepositoryAccessLogViewComponent implements OnInit {
exercise: ProgrammingExercise;
constructor(
- private accountService: AccountService,
public domainService: DomainService,
private route: ActivatedRoute,
private programmingExerciseParticipationService: ProgrammingExerciseParticipationService,
- private programmingExerciseService: ProgrammingExerciseService,
private router: Router,
private alertService: AlertService,
) {}
@@ -43,32 +39,27 @@ export class VcsRepositoryAccessLogViewComponent implements OnInit {
this.paramSub = this.route.params.subscribe((params) => {
const participationId = Number(params['participationId']);
const exerciseId = Number(params['exerciseId']);
- const reposiotryType = params['repositoryType'];
+ const repositoryType = params['repositoryType'];
if (participationId) {
this.loadVcsAccessLogForParticipation(participationId);
} else {
- this.loadVcsAccessLog(exerciseId, reposiotryType);
+ this.loadVcsAccessLog(exerciseId, repositoryType);
}
});
}
- private loadVcsAccessLogForParticipation(participationId: number) {
- this.programmingExerciseParticipationService.getVcsAccessLogForParticipation(participationId).subscribe({
- next: (next: VcsAccessLogDTO[] | undefined) => {
- if (next) {
- this.vcsAccessLogEntries = next;
- this.dialogErrorSource.next('');
- }
- },
- error: (error: HttpErrorResponse) => {
- this.dialogErrorSource.next(error.message);
- this.alertService.error('artemisApp.repository.vcsAccessLog.error');
- },
- });
+ public loadVcsAccessLogForParticipation(participationId: number) {
+ const accessLogEntries: Observable = this.programmingExerciseParticipationService.getVcsAccessLogForParticipation(participationId);
+ this.extractEntries(accessLogEntries);
+ }
+
+ public loadVcsAccessLog(exerciseId: number, repositoryType: any) {
+ const accessLogEntries: Observable = this.programmingExerciseParticipationService.getVcsAccessLogForRepository(exerciseId, repositoryType);
+ this.extractEntries(accessLogEntries);
}
- private loadVcsAccessLog(exerciseId: number, reposiotryType: any) {
- this.programmingExerciseParticipationService.getVcsAccessLogForExerciseRepository(exerciseId, reposiotryType).subscribe({
+ private extractEntries(accessLogEntries: Observable) {
+ accessLogEntries.subscribe({
next: (next: VcsAccessLogDTO[] | undefined) => {
if (next) {
this.vcsAccessLogEntries = next;
From 326376e1438cbd15b07d4c35b25e2d74eefac89d Mon Sep 17 00:00:00 2001
From: entholzer
Date: Tue, 27 Aug 2024 10:31:09 +0200
Subject: [PATCH 13/50] add java integration tests
---
.../artemis/service/VcsAccessLogService.java | 4 +--
...grammingExerciseParticipationResource.java | 1 -
...gExerciseParticipationIntegrationTest.java | 30 +++++++++++++++++++
3 files changed, 32 insertions(+), 3 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java b/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
index 16c49ab654ce..e99c82c8ab50 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
@@ -1,6 +1,6 @@
package de.tum.in.www1.artemis.service;
-import static de.tum.in.www1.artemis.config.Constants.PROFILE_LOCALVC;
+import static de.tum.in.www1.artemis.config.Constants.PROFILE_CORE;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
@@ -19,7 +19,7 @@
import de.tum.in.www1.artemis.repository.VcsAccessLogRepository;
import de.tum.in.www1.artemis.web.rest.repository.RepositoryActionType;
-@Profile(PROFILE_LOCALVC)
+@Profile(PROFILE_CORE)
@Service
public class VcsAccessLogService {
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
index e89be11c9b70..07a0d5644d32 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
@@ -475,5 +475,4 @@ private boolean shouldHideExamExerciseResults(ProgrammingExerciseStudentParticip
}
return false;
}
-
}
diff --git a/src/test/java/de/tum/in/www1/artemis/exercise/programming/ProgrammingExerciseParticipationIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/exercise/programming/ProgrammingExerciseParticipationIntegrationTest.java
index 5c1f03224682..9d0ffa1ad2d8 100644
--- a/src/test/java/de/tum/in/www1/artemis/exercise/programming/ProgrammingExerciseParticipationIntegrationTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/exercise/programming/ProgrammingExerciseParticipationIntegrationTest.java
@@ -37,12 +37,17 @@
import de.tum.in.www1.artemis.domain.participation.SolutionProgrammingExerciseParticipation;
import de.tum.in.www1.artemis.domain.participation.StudentParticipation;
import de.tum.in.www1.artemis.domain.participation.TemplateProgrammingExerciseParticipation;
+import de.tum.in.www1.artemis.domain.vcstokens.AuthenticationMechanism;
+import de.tum.in.www1.artemis.domain.vcstokens.VcsAccessLog;
import de.tum.in.www1.artemis.participation.ParticipationUtilService;
import de.tum.in.www1.artemis.repository.ParticipationRepository;
import de.tum.in.www1.artemis.repository.ProgrammingExerciseRepository;
import de.tum.in.www1.artemis.repository.ProgrammingExerciseStudentParticipationRepository;
import de.tum.in.www1.artemis.repository.StudentParticipationRepository;
+import de.tum.in.www1.artemis.repository.VcsAccessLogRepository;
import de.tum.in.www1.artemis.web.rest.dto.CommitInfoDTO;
+import de.tum.in.www1.artemis.web.rest.dto.VcsAccessLogDTO;
+import de.tum.in.www1.artemis.web.rest.repository.RepositoryActionType;
class ProgrammingExerciseParticipationIntegrationTest extends AbstractSpringIntegrationIndependentTest {
@@ -61,6 +66,9 @@ class ProgrammingExerciseParticipationIntegrationTest extends AbstractSpringInte
@Autowired
private ParticipationRepository participationRepository;
+ @Autowired
+ private VcsAccessLogRepository vcsAccessLogRepository;
+
@Autowired
private ProgrammingExerciseStudentParticipationRepository programmingExerciseStudentParticipationRepository;
@@ -755,6 +763,28 @@ void retrieveCommitHistoryTutorNotOwningParticipationSuccess() throws Exception
request.getList("/api/programming-exercise-participations/" + participation.getId() + "/commit-history", HttpStatus.OK, CommitInfoDTO.class);
}
+ @Test
+ @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR")
+ void testGetVcsAccessLog() throws Exception {
+ var participation = participationUtilService.addStudentParticipationForProgrammingExercise(programmingExercise, TEST_PREFIX + "instructor1");
+ var user = userRepository.getUser();
+ vcsAccessLogRepository.save(new VcsAccessLog(user, participation, "instructor", "instructorMail@mail.de", RepositoryActionType.READ, AuthenticationMechanism.SSH, "", ""));
+ var li = request.getList(participationsBaseUrl + participation.getId() + "/vcs-access-log", HttpStatus.OK, VcsAccessLogDTO.class);
+ assertThat(li.size()).isEqualTo(1);
+ assertThat(li.getFirst().userId()).isEqualTo(user.getId());
+ }
+
+ @Test
+ @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR")
+ void testGetVcsAccessLogOfTemplateParticipation() throws Exception {
+ var user = userRepository.getUser();
+ vcsAccessLogRepository.save(new VcsAccessLog(user, programmingExercise.getTemplateParticipation(), "instructor", "instructorMail@mail.de", RepositoryActionType.READ,
+ AuthenticationMechanism.SSH, "", ""));
+ var li = request.getList("/api/programming-exercise/" + programmingExercise.getId() + "/vcs-access-log/TEMPLATE", HttpStatus.OK, VcsAccessLogDTO.class);
+ assertThat(li.size()).isEqualTo(1);
+ assertThat(li.getFirst().userId()).isEqualTo(user.getId());
+ }
+
private Result addStudentParticipationWithResult(AssessmentType assessmentType, ZonedDateTime completionDate) {
programmingExerciseParticipation = participationUtilService.addStudentParticipationForProgrammingExercise(programmingExercise, TEST_PREFIX + "student1");
Result r = participationUtilService.addResultToParticipation(assessmentType, completionDate, programmingExerciseParticipation);
From 6725e97fb36e1632ec1ace429933d1a6918ea586 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Tue, 27 Aug 2024 11:37:46 +0200
Subject: [PATCH 14/50] add java integration tests
---
...pository-access-log-view.component.spec.ts | 88 +++++++++++++++++++
...gramming-exercise-participation.service.ts | 3 +
...ing-exercise-participation.service.spec.ts | 17 ++++
3 files changed, 108 insertions(+)
create mode 100644 src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
diff --git a/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts b/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
new file mode 100644
index 000000000000..c0901357be6a
--- /dev/null
+++ b/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
@@ -0,0 +1,88 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ProgrammingExerciseParticipationService } from 'app/exercises/programming/manage/services/programming-exercise-participation.service';
+import { ActivatedRoute } from '@angular/router';
+import { MockProgrammingExerciseParticipationService } from '../../helpers/mocks/service/mock-programming-exercise-participation.service';
+import dayjs from 'dayjs/esm';
+import { of } from 'rxjs';
+import { TranslateService } from '@ngx-translate/core';
+import { ProgrammingExerciseService } from 'app/exercises/programming/manage/services/programming-exercise.service';
+import { MockProgrammingExerciseService } from '../../helpers/mocks/service/mock-programming-exercise.service';
+import { VcsRepositoryAccessLogViewComponent } from 'app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component';
+import { VcsAccessLogDTO } from 'app/entities/vcs-access-log-entry.model';
+import { MockTranslateService } from '../../helpers/mocks/service/mock-translate.service';
+import { AlertService } from 'app/core/util/alert.service';
+import { MockAlertService } from '../../helpers/mocks/service/mock-alert.service';
+
+describe('VcsRepositoryAccessLogViewComponent', () => {
+ let component: VcsRepositoryAccessLogViewComponent;
+ let fixture: ComponentFixture;
+ let programmingExerciseParticipationService: ProgrammingExerciseParticipationService;
+ const usreId = 4;
+ let participationVcsAccessLogSpy: jest.SpyInstance;
+ let repositoryVcsAccessLogSpy: jest.SpyInstance;
+
+ const mockVcsAccessLog: VcsAccessLogDTO[] = [
+ {
+ id: 1,
+ userId: usreId,
+ name: 'authorName',
+ email: 'authorEmail',
+ commitHash: 'abcde',
+ authenticationMechanism: 'SSH',
+ repositoryActionType: 'WRITE',
+ timestamp: dayjs('2021-01-02'),
+ },
+ {
+ id: 2,
+ userId: usreId,
+ name: 'authorName',
+ email: 'authorEmail',
+ commitHash: 'fffee',
+ authenticationMechanism: 'SSH',
+ repositoryActionType: 'READ',
+ timestamp: dayjs('2021-01-03'),
+ },
+ ];
+
+ const route = { params: of({ participationId: '5' }) } as any as ActivatedRoute;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [VcsRepositoryAccessLogViewComponent],
+ providers: [
+ { provide: ActivatedRoute, useValue: route },
+ { provide: ProgrammingExerciseParticipationService, useClass: MockProgrammingExerciseParticipationService },
+ { provide: ProgrammingExerciseService, useClass: MockProgrammingExerciseService },
+ { provide: TranslateService, useClass: MockTranslateService },
+ { provide: AlertService, useClass: MockAlertService },
+ ],
+ })
+ .compileComponents()
+ .then(() => {
+ fixture = TestBed.createComponent(VcsRepositoryAccessLogViewComponent);
+ component = fixture.componentInstance;
+ programmingExerciseParticipationService = fixture.debugElement.injector.get(ProgrammingExerciseParticipationService);
+
+ repositoryVcsAccessLogSpy = jest.spyOn(programmingExerciseParticipationService, 'getVcsAccessLogForRepository').mockReturnValue(of(mockVcsAccessLog));
+ participationVcsAccessLogSpy = jest.spyOn(programmingExerciseParticipationService, 'getVcsAccessLogForParticipation').mockReturnValue(of(mockVcsAccessLog));
+ });
+ });
+
+ it('should load participation vcs access log', () => {
+ // Trigger ngOnInit
+ component.ngOnInit();
+ expect(component).toBeTruthy();
+ expect(participationVcsAccessLogSpy).toHaveBeenCalled();
+ expect(component.vcsAccessLogEntries).toHaveLength(2);
+ expect(component.vcsAccessLogEntries[0].userId).toBe(usreId);
+ });
+
+ it('should load template repository vcs access log', () => {
+ // Trigger ngOnInit
+ route.params = of({ exerciseId: '10', repositoryType: 'TEMPLATE' });
+ component.ngOnInit();
+ expect(repositoryVcsAccessLogSpy).toHaveBeenCalled();
+ expect(component.vcsAccessLogEntries).toHaveLength(2);
+ expect(component.vcsAccessLogEntries[0].userId).toBe(usreId);
+ });
+});
diff --git a/src/test/javascript/spec/helpers/mocks/service/mock-programming-exercise-participation.service.ts b/src/test/javascript/spec/helpers/mocks/service/mock-programming-exercise-participation.service.ts
index 6b0fb6ea0d78..221b6839c4ef 100644
--- a/src/test/javascript/spec/helpers/mocks/service/mock-programming-exercise-participation.service.ts
+++ b/src/test/javascript/spec/helpers/mocks/service/mock-programming-exercise-participation.service.ts
@@ -3,6 +3,7 @@ import { IProgrammingExerciseParticipationService } from 'app/exercises/programm
import { ProgrammingExerciseStudentParticipation } from 'app/entities/participation/programming-exercise-student-participation.model';
import { Result } from 'app/entities/result.model';
import { CommitInfo } from 'app/entities/programming-submission.model';
+import { VcsAccessLogDTO } from 'app/entities/vcs-access-log-entry.model';
export class MockProgrammingExerciseParticipationService implements IProgrammingExerciseParticipationService {
getLatestResultWithFeedback = (participationId: number, withSubmission: boolean) => of({} as Result);
@@ -13,4 +14,6 @@ export class MockProgrammingExerciseParticipationService implements IProgramming
getParticipationRepositoryFilesWithContentAtCommitForCommitDetailsView = (exerciseId: number, participationId: number, commitId: string, repositoryType: string) =>
of(new Map());
checkIfParticipationHasResult = (participationId: number) => of(true);
+ getVcsAccessLogForRepository = (exerciseId: number, repositoryType: string) => of([] as VcsAccessLogDTO[]);
+ getVcsAccessLogForParticipation = (participationId: number) => of([] as VcsAccessLogDTO[]);
}
diff --git a/src/test/javascript/spec/service/programming-exercise-participation.service.spec.ts b/src/test/javascript/spec/service/programming-exercise-participation.service.spec.ts
index 92bff0034faa..1ef9b69501d1 100644
--- a/src/test/javascript/spec/service/programming-exercise-participation.service.spec.ts
+++ b/src/test/javascript/spec/service/programming-exercise-participation.service.spec.ts
@@ -177,4 +177,21 @@ describe('ProgrammingExerciseParticipation Service', () => {
req.flush(files);
tick();
}));
+
+ it('should make GET request to retrieve vcs access log for participation', fakeAsync(() => {
+ const participationId = 42;
+ service.getVcsAccessLogForParticipation(participationId).subscribe();
+ const expectedURL = `${resourceUrlParticipations}${participationId}/vcs-access-log`;
+ httpMock.expectOne({ method: 'GET', url: expectedURL });
+ tick();
+ }));
+
+ it('should make GET request to retrieve vcs access log for the template repository', fakeAsync(() => {
+ const exerciseId = 42;
+ const repositoryType = 'TEMPLATE';
+ service.getVcsAccessLogForRepository(exerciseId, repositoryType).subscribe();
+ const expectedURL = `${resourceUrl}${exerciseId}/vcs-access-log/${repositoryType}`;
+ httpMock.expectOne({ method: 'GET', url: expectedURL });
+ tick();
+ }));
});
From ebf8872ed8df3737866fcdabe896c23adff779e6 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Tue, 27 Aug 2024 11:43:36 +0200
Subject: [PATCH 15/50] fix java docs
---
.../in/www1/artemis/service/programming/RepositoryService.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/main/java/de/tum/in/www1/artemis/service/programming/RepositoryService.java b/src/main/java/de/tum/in/www1/artemis/service/programming/RepositoryService.java
index 6470c32d3885..fb75d287d507 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/programming/RepositoryService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/programming/RepositoryService.java
@@ -473,6 +473,7 @@ public void pullChanges(Repository repository) {
*
* @param repository for which to execute the commit.
* @param user the user who has committed the changes in the online editor
+ * @param domainId the id of the domain Object (participation) owning the repository
* @throws GitAPIException if the staging/committing process fails.
*/
public void commitChanges(Repository repository, User user, Long domainId) throws GitAPIException {
From bdfb8816e0a2b4b260d5e477bcb0400c84b1ad41 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Tue, 27 Aug 2024 14:06:49 +0200
Subject: [PATCH 16/50] only show to instructors in repository view
(course-management)
---
.../app/localvc/repository-view/repository-view.component.html | 2 +-
.../app/localvc/repository-view/repository-view.component.ts | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/main/webapp/app/localvc/repository-view/repository-view.component.html b/src/main/webapp/app/localvc/repository-view/repository-view.component.html
index 84eda91f8370..7b67b23bad7d 100644
--- a/src/main/webapp/app/localvc/repository-view/repository-view.component.html
+++ b/src/main/webapp/app/localvc/repository-view/repository-view.component.html
@@ -34,7 +34,7 @@
}
- @if (routeVcsAccessLog) {
+ @if (routeVcsAccessLog && vcsAccessLogEnabled) {
diff --git a/src/main/webapp/app/localvc/repository-view/repository-view.component.ts b/src/main/webapp/app/localvc/repository-view/repository-view.component.ts
index 828aae28ae59..187a28a63494 100644
--- a/src/main/webapp/app/localvc/repository-view/repository-view.component.ts
+++ b/src/main/webapp/app/localvc/repository-view/repository-view.component.ts
@@ -43,7 +43,7 @@ export class RepositoryViewComponent implements OnInit, OnDestroy {
routeVcsAccessLog: string;
repositoryUri: string;
repositoryType: ProgrammingExerciseInstructorRepositoryType | 'USER';
-
+ vcsAccessLogEnabled = false;
result: Result;
resultHasInlineFeedback = false;
showInlineFeedback = false;
@@ -88,6 +88,7 @@ export class RepositoryViewComponent implements OnInit, OnDestroy {
const participationId = Number(params['participationId']);
this.repositoryType = participationId ? 'USER' : params['repositoryType'];
this.routeVcsAccessLog = this.router.url + '/vcs-access-log';
+ this.vcsAccessLogEnabled = this.router.url.includes('course-management') && params['repositoryType'] != 'TESTS';
if (this.repositoryType === 'USER') {
this.loadStudentParticipation(participationId);
From 65a6d4af72048feed16a797dbf6c6441c6ccac02 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Tue, 27 Aug 2024 14:25:59 +0200
Subject: [PATCH 17/50] fix typo
---
.../vcs-repository-access-log-view.component.spec.ts | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts b/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
index c0901357be6a..5c05d385a0d5 100644
--- a/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
+++ b/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
@@ -17,14 +17,14 @@ describe('VcsRepositoryAccessLogViewComponent', () => {
let component: VcsRepositoryAccessLogViewComponent;
let fixture: ComponentFixture;
let programmingExerciseParticipationService: ProgrammingExerciseParticipationService;
- const usreId = 4;
+ const userId = 4;
let participationVcsAccessLogSpy: jest.SpyInstance;
let repositoryVcsAccessLogSpy: jest.SpyInstance;
const mockVcsAccessLog: VcsAccessLogDTO[] = [
{
id: 1,
- userId: usreId,
+ userId: userId,
name: 'authorName',
email: 'authorEmail',
commitHash: 'abcde',
@@ -34,7 +34,7 @@ describe('VcsRepositoryAccessLogViewComponent', () => {
},
{
id: 2,
- userId: usreId,
+ userId: userId,
name: 'authorName',
email: 'authorEmail',
commitHash: 'fffee',
@@ -74,7 +74,7 @@ describe('VcsRepositoryAccessLogViewComponent', () => {
expect(component).toBeTruthy();
expect(participationVcsAccessLogSpy).toHaveBeenCalled();
expect(component.vcsAccessLogEntries).toHaveLength(2);
- expect(component.vcsAccessLogEntries[0].userId).toBe(usreId);
+ expect(component.vcsAccessLogEntries[0].userId).toBe(userId);
});
it('should load template repository vcs access log', () => {
@@ -83,6 +83,6 @@ describe('VcsRepositoryAccessLogViewComponent', () => {
component.ngOnInit();
expect(repositoryVcsAccessLogSpy).toHaveBeenCalled();
expect(component.vcsAccessLogEntries).toHaveLength(2);
- expect(component.vcsAccessLogEntries[0].userId).toBe(usreId);
+ expect(component.vcsAccessLogEntries[0].userId).toBe(userId);
});
});
From bfce78ab67e5b41b98edcbd37997b85c1db1cc4f Mon Sep 17 00:00:00 2001
From: entholzer
Date: Tue, 27 Aug 2024 14:40:56 +0200
Subject: [PATCH 18/50] increase size of name
---
.../config/liquibase/changelog/20240804144500_changelog.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/resources/config/liquibase/changelog/20240804144500_changelog.xml b/src/main/resources/config/liquibase/changelog/20240804144500_changelog.xml
index 14e939c61d64..72b4d0498221 100644
--- a/src/main/resources/config/liquibase/changelog/20240804144500_changelog.xml
+++ b/src/main/resources/config/liquibase/changelog/20240804144500_changelog.xml
@@ -16,7 +16,7 @@
-
+
From ca04dc18aeb9deee0b0794e2c8b5e6430e23f0a5 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Tue, 27 Aug 2024 21:39:06 +0200
Subject: [PATCH 19/50] cleanup entries after 120 days
---
.../repository/VcsAccessLogRepository.java | 14 +++++++
.../AutomaticVcsAccessLogCleanupService.java | 40 +++++++++++++++++++
.../resources/config/application-prod.yml | 3 +-
3 files changed, 55 insertions(+), 2 deletions(-)
create mode 100644 src/main/java/de/tum/in/www1/artemis/service/scheduled/AutomaticVcsAccessLogCleanupService.java
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
index a6c9b049f448..a068f4d43d07 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
@@ -2,6 +2,7 @@
import static de.tum.in.www1.artemis.config.Constants.PROFILE_CORE;
+import java.time.ZonedDateTime;
import java.util.List;
import java.util.Optional;
@@ -53,4 +54,17 @@ public interface VcsAccessLogRepository extends ArtemisJpaRepository findAllByParticipationId(long participationId);
+
+ /**
+ * Retrieves a list of {@link VcsAccessLog} entities associated with the specified participation ID.
+ * The results are ordered by the log ID in ascending order.
+ *
+ * @return a list of {@link VcsAccessLog} entities for the given participation ID, sorted by log ID in ascending order.
+ */
+ @Query("""
+ SELECT vcsAccessLog.id
+ FROM VcsAccessLog vcsAccessLog
+ WHERE vcsAccessLog.timestamp < :date
+ """)
+ List findAllIdsBeforeDate(ZonedDateTime date);
}
diff --git a/src/main/java/de/tum/in/www1/artemis/service/scheduled/AutomaticVcsAccessLogCleanupService.java b/src/main/java/de/tum/in/www1/artemis/service/scheduled/AutomaticVcsAccessLogCleanupService.java
new file mode 100644
index 000000000000..f650be7aca28
--- /dev/null
+++ b/src/main/java/de/tum/in/www1/artemis/service/scheduled/AutomaticVcsAccessLogCleanupService.java
@@ -0,0 +1,40 @@
+package de.tum.in.www1.artemis.service.scheduled;
+
+import static de.tum.in.www1.artemis.config.Constants.PROFILE_SCHEDULING;
+
+import java.time.ZonedDateTime;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Profile;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+
+import de.tum.in.www1.artemis.repository.VcsAccessLogRepository;
+
+@Service
+@Profile(PROFILE_SCHEDULING)
+public class AutomaticVcsAccessLogCleanupService {
+
+ private static final Logger log = LoggerFactory.getLogger(AutomaticVcsAccessLogCleanupService.class);
+
+ private final VcsAccessLogRepository vcsAccessLogRepository;
+
+ @Value("${artemis.audit-events.retention-period:120}")
+ private int vcsAccessLogRetentionPeriod;
+
+ public AutomaticVcsAccessLogCleanupService(VcsAccessLogRepository vcsAccessLogRepository) {
+ this.vcsAccessLogRepository = vcsAccessLogRepository;
+ }
+
+ /**
+ * Deletes all vcs access log entries from the database which have a timestamp older than vcsAccessLogRetentionPeriod days (120 by default)
+ */
+ @Scheduled(cron = "0 30 2 * * *") // execute this every night at 2:30:00 am
+ public void cleanup() {
+ var outDatedAccessLogs = vcsAccessLogRepository.findAllIdsBeforeDate(ZonedDateTime.now().minusDays(vcsAccessLogRetentionPeriod));
+ log.info("Scheduled deletion of expired access log entries: deleting {} vcs access log entries", outDatedAccessLogs.size());
+ vcsAccessLogRepository.deleteAllById(outDatedAccessLogs);
+ }
+}
diff --git a/src/main/resources/config/application-prod.yml b/src/main/resources/config/application-prod.yml
index 5fb8190cc112..45cc3f82b809 100644
--- a/src/main/resources/config/application-prod.yml
+++ b/src/main/resources/config/application-prod.yml
@@ -103,8 +103,7 @@ jhipster:
port: 5000
ring-buffer-size: 512
audit-events:
- retention-period: 120 # Number of days before audit events are deleted.
-
+ retention-period: 120 # Number of days before audit events are deleted, and Number of days before vcs access logs are deleted.
# Properties to be exposed on the /info management endpoint
info:
guided-tour:
From 9564657b5c44eef4ed01bb2d77dfc506c0f690e2 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Wed, 28 Aug 2024 19:00:34 +0200
Subject: [PATCH 20/50] fix params
---
.../service/connectors/localvc/LocalVCServletService.java | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java
index 95ff335abf5c..002258aac55b 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java
@@ -406,7 +406,10 @@ private UsernameAndPassword extractUsernameAndPassword(String authorizationHeade
* @param user The user that wants to access the repository.
* @param exercise The exercise the repository belongs to.
* @param repositoryActionType The type of the action the user wants to perform.
+ * @param authenticationMechanism The authentication mechanism used by the user to authenticate to the repository
+ * @param ipAddress The ip address of the user
* @param localVCRepositoryUri The URI of the local repository.
+ *
* @throws LocalVCForbiddenException If the user is not allowed to access the repository.
*/
public void authorizeUser(String repositoryTypeOrUserName, User user, ProgrammingExercise exercise, RepositoryActionType repositoryActionType,
From f7271298eb4adab79f478e114f35d520343665a9 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Wed, 28 Aug 2024 19:03:47 +0200
Subject: [PATCH 21/50] fix java docs in Repository
---
.../www1/artemis/repository/VcsAccessLogRepository.java | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
index a068f4d43d07..3dcb8f912e5c 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
@@ -53,18 +53,20 @@ public interface VcsAccessLogRepository extends ArtemisJpaRepository findAllByParticipationId(long participationId);
+ List findAllByParticipationId(@Param("participationId") long participationId);
/**
* Retrieves a list of {@link VcsAccessLog} entities associated with the specified participation ID.
* The results are ordered by the log ID in ascending order.
*
- * @return a list of {@link VcsAccessLog} entities for the given participation ID, sorted by log ID in ascending order.
+ * @param date The date before which all log ids should be fetched
+ *
+ * @return a list of ids of the access logs, which have a timestamp before the date
*/
@Query("""
SELECT vcsAccessLog.id
FROM VcsAccessLog vcsAccessLog
WHERE vcsAccessLog.timestamp < :date
""")
- List findAllIdsBeforeDate(ZonedDateTime date);
+ List findAllIdsBeforeDate(@Param("date") ZonedDateTime date);
}
From 0c01195dbb1298083198408b9020274061b8a111 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Wed, 28 Aug 2024 19:06:05 +0200
Subject: [PATCH 22/50] fix authorization
---
.../programming/ProgrammingExerciseParticipationResource.java | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
index 07a0d5644d32..0577564fb427 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
@@ -37,6 +37,7 @@
import de.tum.in.www1.artemis.repository.StudentExamRepository;
import de.tum.in.www1.artemis.repository.VcsAccessLogRepository;
import de.tum.in.www1.artemis.security.Role;
+import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastEditor;
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastInstructor;
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastStudent;
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastTutor;
@@ -425,7 +426,7 @@ else if (repositoryType != null) {
* @throws BadRequestAlertException if the repository type is invalid
*/
@GetMapping("programming-exercise/{exerciseId}/vcs-access-log/{repositoryType}")
- @EnforceAtLeastStudent
+ @EnforceAtLeastEditor
public ResponseEntity> getVcsAccessLogForExerciseRepository(@PathVariable long exerciseId, @PathVariable RepositoryType repositoryType) {
if (repositoryType != RepositoryType.TEMPLATE && repositoryType != RepositoryType.SOLUTION) {
From 5905ce2c6f158c42fc3f4cf22523209da838a354 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Wed, 28 Aug 2024 19:21:47 +0200
Subject: [PATCH 23/50] unsubscribe subscription
---
.../vcs-repository-access-log-view.component.ts | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts b/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
index 1c4458b8d58b..cc322768e025 100644
--- a/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
+++ b/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
@@ -1,4 +1,4 @@
-import { Component, OnInit } from '@angular/core';
+import { Component, OnDestroy, OnInit } from '@angular/core';
import { Observable, Subject, Subscription } from 'rxjs';
import { ActivatedRoute, Router } from '@angular/router';
import { DomainService } from 'app/exercises/programming/shared/code-editor/service/code-editor-domain.service';
@@ -16,7 +16,7 @@ import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module';
standalone: true,
imports: [ArtemisSharedCommonModule],
})
-export class VcsRepositoryAccessLogViewComponent implements OnInit {
+export class VcsRepositoryAccessLogViewComponent implements OnInit, OnDestroy {
participationId: number;
vcsAccessLogEntries: VcsAccessLogDTO[];
protected dialogErrorSource = new Subject();
@@ -48,6 +48,10 @@ export class VcsRepositoryAccessLogViewComponent implements OnInit {
});
}
+ ngOnDestroy(): void {
+ this.paramSub.unsubscribe();
+ }
+
public loadVcsAccessLogForParticipation(participationId: number) {
const accessLogEntries: Observable = this.programmingExerciseParticipationService.getVcsAccessLogForParticipation(participationId);
this.extractEntries(accessLogEntries);
From 3343cf270ec11d212220f4b1bbc96efa034aef22 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Wed, 28 Aug 2024 19:31:40 +0200
Subject: [PATCH 24/50] remove comments
---
.../localvc/vcs-repository-access-log-view.component.spec.ts | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts b/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
index 5c05d385a0d5..f7202950f86a 100644
--- a/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
+++ b/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
@@ -69,7 +69,6 @@ describe('VcsRepositoryAccessLogViewComponent', () => {
});
it('should load participation vcs access log', () => {
- // Trigger ngOnInit
component.ngOnInit();
expect(component).toBeTruthy();
expect(participationVcsAccessLogSpy).toHaveBeenCalled();
@@ -78,7 +77,6 @@ describe('VcsRepositoryAccessLogViewComponent', () => {
});
it('should load template repository vcs access log', () => {
- // Trigger ngOnInit
route.params = of({ exerciseId: '10', repositoryType: 'TEMPLATE' });
component.ngOnInit();
expect(repositoryVcsAccessLogSpy).toHaveBeenCalled();
From 35902d97ccea1768507974f5569167e4cf0f2e38 Mon Sep 17 00:00:00 2001
From: Simon Entholzer <33342534+SimonEntholzer@users.noreply.github.com>
Date: Fri, 30 Aug 2024 11:51:50 +0200
Subject: [PATCH 25/50] Update src/main/resources/config/application-prod.yml
Co-authored-by: Yannik S
---
src/main/resources/config/application-prod.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/resources/config/application-prod.yml b/src/main/resources/config/application-prod.yml
index 45cc3f82b809..f3a3b217a25f 100644
--- a/src/main/resources/config/application-prod.yml
+++ b/src/main/resources/config/application-prod.yml
@@ -103,7 +103,7 @@ jhipster:
port: 5000
ring-buffer-size: 512
audit-events:
- retention-period: 120 # Number of days before audit events are deleted, and Number of days before vcs access logs are deleted.
+ retention-period: 120 # Number of days before audit events and vsc access logs are deleted.
# Properties to be exposed on the /info management endpoint
info:
guided-tour:
From 22a0ec5b734b09548eb8d5508827a54c9da979e3 Mon Sep 17 00:00:00 2001
From: Simon Entholzer <33342534+SimonEntholzer@users.noreply.github.com>
Date: Fri, 30 Aug 2024 12:14:34 +0200
Subject: [PATCH 26/50] Update src/main/webapp/i18n/en/repository.json
Co-authored-by: Yannik S
---
src/main/webapp/i18n/en/repository.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/webapp/i18n/en/repository.json b/src/main/webapp/i18n/en/repository.json
index 4c45f606751a..67df637c83a9 100644
--- a/src/main/webapp/i18n/en/repository.json
+++ b/src/main/webapp/i18n/en/repository.json
@@ -27,7 +27,7 @@
"userId": "User Id",
"author": "Author",
"actionType": "Action Type",
- "authMechanism": "Authentication",
+ "authMechanism": "Accesstype",
"commitHash": "Commit Hash",
"timeStamp": "Timestamp",
"error": "VCS Access log could not be retrieved"
From 053e20892bc0a830507f1e09f90df35d0896280b Mon Sep 17 00:00:00 2001
From: Simon Entholzer <33342534+SimonEntholzer@users.noreply.github.com>
Date: Fri, 30 Aug 2024 12:14:45 +0200
Subject: [PATCH 27/50] Update src/main/webapp/i18n/de/repository.json
Co-authored-by: Yannik S
---
src/main/webapp/i18n/de/repository.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/webapp/i18n/de/repository.json b/src/main/webapp/i18n/de/repository.json
index cc8fb238608b..081939ccb0cd 100644
--- a/src/main/webapp/i18n/de/repository.json
+++ b/src/main/webapp/i18n/de/repository.json
@@ -27,7 +27,7 @@
"userId": "Benutzer Id",
"author": "Author",
"actionType": "Aktionstyp",
- "authMechanism": "Authentifizierung",
+ "authMechanism": "Zugriffsart",
"commitHash": "Commit Hash",
"timeStamp": "Zeitstempel",
"error": "VCS-Zugriffsprotokoll konnte nicht abgerufen werden"
From 77200811f0f85203c53777d582e8af484b1831bd Mon Sep 17 00:00:00 2001
From: Simon Entholzer <33342534+SimonEntholzer@users.noreply.github.com>
Date: Fri, 30 Aug 2024 12:14:56 +0200
Subject: [PATCH 28/50] Update src/main/webapp/i18n/de/repository.json
Co-authored-by: Yannik S
---
src/main/webapp/i18n/de/repository.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/webapp/i18n/de/repository.json b/src/main/webapp/i18n/de/repository.json
index 081939ccb0cd..6bd6d6d28842 100644
--- a/src/main/webapp/i18n/de/repository.json
+++ b/src/main/webapp/i18n/de/repository.json
@@ -25,7 +25,7 @@
"title": "VCS-Zugriffsprotokoll",
"openVcsAccessLog": "Zugriffsprotokoll öffnen",
"userId": "Benutzer Id",
- "author": "Author",
+ "author": "Autor",
"actionType": "Aktionstyp",
"authMechanism": "Zugriffsart",
"commitHash": "Commit Hash",
From 56a53206935f39aacd54c8776d22d2da424122f5 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Fri, 30 Aug 2024 12:34:48 +0200
Subject: [PATCH 29/50] added improved java docs and added more logging
---
.../de/tum/in/www1/artemis/service/VcsAccessLogService.java | 2 +-
.../service/connectors/localvc/LocalVCServletService.java | 3 +--
.../ProgrammingExerciseParticipationResource.java | 6 ++++++
3 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java b/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
index e99c82c8ab50..465afe7cd059 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
@@ -40,7 +40,7 @@ public class VcsAccessLogService {
* @param user The user accessing the repository
* @param participation The participation which owns the repository
* @param actionType The action type: READ or WRITE
- * @param authenticationMechanism The used authentication mechanism: password, vcs token (user/participation) or SSH
+ * @param authenticationMechanism The used authentication mechanism: password, vcs token (user/participation), SSH or code editor
* @param commitHash The latest commit hash
* @param ipAddress The ip address of the user accessing the repository
*/
diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java
index 002258aac55b..5546ac2ae982 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java
@@ -264,14 +264,13 @@ private AuthenticationMechanism resolveAuthenticationMechanism(String authorizat
UsernameAndPassword usernameAndPassword = extractUsernameAndPassword(authorizationHeader);
String password = usernameAndPassword.password();
- if (!password.startsWith("vcpat")) {
+ if (!password.startsWith(TOKEN_PREFIX)) {
return AuthenticationMechanism.PASSWORD;
}
if (password.equals(user.getVcsAccessToken())) {
return AuthenticationMechanism.USER_VCS_ACCESS_TOKEN;
}
return AuthenticationMechanism.PARTICIPATION_VCS_ACCESS_TOKEN;
-
}
private User authenticateUser(String authorizationHeader, ProgrammingExercise exercise, LocalVCRepositoryUri localVCRepositoryUri) throws LocalVCAuthException {
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
index 0577564fb427..2f681685a870 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
@@ -11,6 +11,8 @@
import java.util.stream.Collectors;
import org.eclipse.jgit.api.errors.GitAPIException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Profile;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
@@ -59,6 +61,8 @@
@RequestMapping("api/")
public class ProgrammingExerciseParticipationResource {
+ private static final Logger log = LoggerFactory.getLogger(ProgrammingExerciseParticipationResource.class);
+
private static final String ENTITY_NAME = "programmingExerciseParticipation";
private final ParticipationRepository participationRepository;
@@ -324,6 +328,7 @@ public ResponseEntity> getCommitHistoryForParticipationRepo(
public ResponseEntity> getVcsAccessLogForParticipationRepo(@PathVariable long participationId) {
ProgrammingExerciseStudentParticipation participation = programmingExerciseStudentParticipationRepository.findByIdElseThrow(participationId);
participationAuthCheckService.checkCanAccessParticipationElseThrow(participation);
+ log.info("Fetching VCS access logs for participation ID: {}", participationId);
List vcsAccessLogs = vcsAccessLogRepository.findAllByParticipationId(participationId);
var vcsAccessLogDTOs = vcsAccessLogs.stream().map(VcsAccessLogDTO::of).toList();
return ResponseEntity.ok(vcsAccessLogDTOs);
@@ -434,6 +439,7 @@ public ResponseEntity> getVcsAccessLogForExerciseRepositor
}
ProgrammingExercise programmingExercise = programmingExerciseRepository.findByIdWithTemplateAndSolutionParticipationAndAuxiliaryRepositoriesElseThrow(exerciseId);
authCheckService.checkHasAtLeastRoleForExerciseElseThrow(Role.INSTRUCTOR, programmingExercise, null);
+ log.info("Fetching VCS access logs for exercise ID: {} and repository type: {}", exerciseId, repositoryType);
var participation = repositoryType == RepositoryType.TEMPLATE ? programmingExercise.getTemplateParticipation() : programmingExercise.getSolutionParticipation();
From c8f9165610eb5345f01494a2f5d21d106705f094 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Tue, 3 Sep 2024 15:32:27 +0200
Subject: [PATCH 30/50] improve client test
---
.../localvc/vcs-repository-access-log-view.component.spec.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts b/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
index f7202950f86a..961d3726c0dd 100644
--- a/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
+++ b/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
@@ -79,7 +79,7 @@ describe('VcsRepositoryAccessLogViewComponent', () => {
it('should load template repository vcs access log', () => {
route.params = of({ exerciseId: '10', repositoryType: 'TEMPLATE' });
component.ngOnInit();
- expect(repositoryVcsAccessLogSpy).toHaveBeenCalled();
+ expect(repositoryVcsAccessLogSpy).toHaveBeenCalledOnce();
expect(component.vcsAccessLogEntries).toHaveLength(2);
expect(component.vcsAccessLogEntries[0].userId).toBe(userId);
});
From 030b03da7ae4c8de1f153cd8ef54c2080d7cd39a Mon Sep 17 00:00:00 2001
From: entholzer
Date: Tue, 3 Sep 2024 16:04:19 +0200
Subject: [PATCH 31/50] only show button to instructors (and admins)
---
.../programming-exercise-management-routing.module.ts | 2 +-
.../repository-view/repository-view.component.html | 2 +-
.../localvc/repository-view/repository-view.component.ts | 9 ++++++---
.../vcs-repository-access-log-view.component.ts | 2 +-
4 files changed, 9 insertions(+), 6 deletions(-)
diff --git a/src/main/webapp/app/exercises/programming/manage/programming-exercise-management-routing.module.ts b/src/main/webapp/app/exercises/programming/manage/programming-exercise-management-routing.module.ts
index a70649028b2e..cf949670dcf9 100644
--- a/src/main/webapp/app/exercises/programming/manage/programming-exercise-management-routing.module.ts
+++ b/src/main/webapp/app/exercises/programming/manage/programming-exercise-management-routing.module.ts
@@ -188,7 +188,7 @@ export const routes: Routes = [
path: ':courseId/programming-exercises/:exerciseId/repository/:repositoryType/vcs-access-log',
component: VcsRepositoryAccessLogViewComponent,
data: {
- authorities: [Authority.ADMIN, Authority.INSTRUCTOR, Authority.EDITOR],
+ authorities: [Authority.ADMIN, Authority.INSTRUCTOR],
pageTitle: 'artemisApp.repository.title',
flushRepositoryCacheAfter: 900000, // 15 min
participationCache: {},
diff --git a/src/main/webapp/app/localvc/repository-view/repository-view.component.html b/src/main/webapp/app/localvc/repository-view/repository-view.component.html
index 7b67b23bad7d..71b33f283097 100644
--- a/src/main/webapp/app/localvc/repository-view/repository-view.component.html
+++ b/src/main/webapp/app/localvc/repository-view/repository-view.component.html
@@ -34,7 +34,7 @@
}
- @if (routeVcsAccessLog && vcsAccessLogEnabled) {
+ @if (routeVcsAccessLog && enableVcsAccessLogButton && allowVcsAccessLogButton) {
diff --git a/src/main/webapp/app/localvc/repository-view/repository-view.component.ts b/src/main/webapp/app/localvc/repository-view/repository-view.component.ts
index 9aa6081ea61a..3c20172177d8 100644
--- a/src/main/webapp/app/localvc/repository-view/repository-view.component.ts
+++ b/src/main/webapp/app/localvc/repository-view/repository-view.component.ts
@@ -43,7 +43,8 @@ export class RepositoryViewComponent implements OnInit, OnDestroy {
routeVcsAccessLog: string;
repositoryUri: string;
repositoryType: ProgrammingExerciseInstructorRepositoryType | 'USER';
- vcsAccessLogEnabled = false;
+ enableVcsAccessLogButton = false;
+ allowVcsAccessLogButton = false;
result: Result;
resultHasInlineFeedback = false;
showInlineFeedback = false;
@@ -88,8 +89,7 @@ export class RepositoryViewComponent implements OnInit, OnDestroy {
const participationId = Number(params['participationId']);
this.repositoryType = participationId ? 'USER' : params['repositoryType'];
this.routeVcsAccessLog = this.router.url + '/vcs-access-log';
- this.vcsAccessLogEnabled = this.router.url.includes('course-management') && params['repositoryType'] != 'TESTS';
-
+ this.enableVcsAccessLogButton = this.router.url.includes('course-management') && params['repositoryType'] !== 'TESTS';
if (this.repositoryType === 'USER') {
this.loadStudentParticipation(participationId);
} else {
@@ -126,6 +126,7 @@ export class RepositoryViewComponent implements OnInit, OnDestroy {
this.participationCouldNotBeFetched = true;
this.loadingParticipation = false;
}
+ this.allowVcsAccessLogButton = this.accountService.isAtLeastInstructorInCourse(this.getCourseFromExercise(this.exercise));
}),
)
.subscribe({
@@ -150,6 +151,8 @@ export class RepositoryViewComponent implements OnInit, OnDestroy {
this.domainService.setDomain([DomainType.PARTICIPATION, participationWithResults]);
this.participation = participationWithResults;
this.exercise = this.participation.exercise as ProgrammingExercise;
+ this.allowVcsAccessLogButton = this.accountService.isAtLeastInstructorInCourse(this.getCourseFromExercise(this.exercise));
+ console.log(this.allowVcsAccessLogButton);
this.repositoryUri = this.participation.repositoryUri!;
}),
)
diff --git a/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts b/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
index cc322768e025..7da4cd407a8e 100644
--- a/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
+++ b/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
@@ -2,7 +2,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
import { Observable, Subject, Subscription } from 'rxjs';
import { ActivatedRoute, Router } from '@angular/router';
import { DomainService } from 'app/exercises/programming/shared/code-editor/service/code-editor-domain.service';
-import { ProgrammingExercise } from 'app/entities/programming-exercise.model';
+import { ProgrammingExercise } from 'app/entities/programming/programming-exercise.model';
import { ProgrammingExerciseStudentParticipation } from 'app/entities/participation/programming-exercise-student-participation.model';
import { ProgrammingExerciseParticipationService } from 'app/exercises/programming/manage/services/programming-exercise-participation.service';
import { VcsAccessLogDTO } from 'app/entities/vcs-access-log-entry.model';
From cc13acd962ec48389425d0fd79b9b1fd4a8648b2 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Tue, 3 Sep 2024 16:06:36 +0200
Subject: [PATCH 32/50] remove console log
---
.../repository-view.component.html | 4 ++--
.../repository-view/repository-view.component.ts | 15 +++++++--------
2 files changed, 9 insertions(+), 10 deletions(-)
diff --git a/src/main/webapp/app/localvc/repository-view/repository-view.component.html b/src/main/webapp/app/localvc/repository-view/repository-view.component.html
index 71b33f283097..0318f6105969 100644
--- a/src/main/webapp/app/localvc/repository-view/repository-view.component.html
+++ b/src/main/webapp/app/localvc/repository-view/repository-view.component.html
@@ -34,8 +34,8 @@
}
- @if (routeVcsAccessLog && enableVcsAccessLogButton && allowVcsAccessLogButton) {
-
+ @if (vcsAccessLogRoute && enableVcsAccessLog && allowVcsAccessLog) {
+
diff --git a/src/main/webapp/app/localvc/repository-view/repository-view.component.ts b/src/main/webapp/app/localvc/repository-view/repository-view.component.ts
index 3c20172177d8..138d8809aa15 100644
--- a/src/main/webapp/app/localvc/repository-view/repository-view.component.ts
+++ b/src/main/webapp/app/localvc/repository-view/repository-view.component.ts
@@ -40,11 +40,11 @@ export class RepositoryViewComponent implements OnInit, OnDestroy {
participationCouldNotBeFetched = false;
showEditorInstructions = true;
routeCommitHistory: string;
- routeVcsAccessLog: string;
+ vcsAccessLogRoute: string;
repositoryUri: string;
repositoryType: ProgrammingExerciseInstructorRepositoryType | 'USER';
- enableVcsAccessLogButton = false;
- allowVcsAccessLogButton = false;
+ enableVcsAccessLog = false;
+ allowVcsAccessLog = false;
result: Result;
resultHasInlineFeedback = false;
showInlineFeedback = false;
@@ -88,8 +88,8 @@ export class RepositoryViewComponent implements OnInit, OnDestroy {
const exerciseId = Number(params['exerciseId']);
const participationId = Number(params['participationId']);
this.repositoryType = participationId ? 'USER' : params['repositoryType'];
- this.routeVcsAccessLog = this.router.url + '/vcs-access-log';
- this.enableVcsAccessLogButton = this.router.url.includes('course-management') && params['repositoryType'] !== 'TESTS';
+ this.vcsAccessLogRoute = this.router.url + '/vcs-access-log';
+ this.enableVcsAccessLog = this.router.url.includes('course-management') && params['repositoryType'] !== 'TESTS';
if (this.repositoryType === 'USER') {
this.loadStudentParticipation(participationId);
} else {
@@ -126,7 +126,7 @@ export class RepositoryViewComponent implements OnInit, OnDestroy {
this.participationCouldNotBeFetched = true;
this.loadingParticipation = false;
}
- this.allowVcsAccessLogButton = this.accountService.isAtLeastInstructorInCourse(this.getCourseFromExercise(this.exercise));
+ this.allowVcsAccessLog = this.accountService.isAtLeastInstructorInCourse(this.getCourseFromExercise(this.exercise));
}),
)
.subscribe({
@@ -151,8 +151,7 @@ export class RepositoryViewComponent implements OnInit, OnDestroy {
this.domainService.setDomain([DomainType.PARTICIPATION, participationWithResults]);
this.participation = participationWithResults;
this.exercise = this.participation.exercise as ProgrammingExercise;
- this.allowVcsAccessLogButton = this.accountService.isAtLeastInstructorInCourse(this.getCourseFromExercise(this.exercise));
- console.log(this.allowVcsAccessLogButton);
+ this.allowVcsAccessLog = this.accountService.isAtLeastInstructorInCourse(this.getCourseFromExercise(this.exercise));
this.repositoryUri = this.participation.repositoryUri!;
}),
)
From 6c2e34b13ec6ceca90239f4d0aac6b94cc9cb56b Mon Sep 17 00:00:00 2001
From: entholzer
Date: Tue, 3 Sep 2024 16:12:30 +0200
Subject: [PATCH 33/50] add docs and return value annotation
---
.../programming-exercise-participation.service.ts | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/src/main/webapp/app/exercises/programming/manage/services/programming-exercise-participation.service.ts b/src/main/webapp/app/exercises/programming/manage/services/programming-exercise-participation.service.ts
index 2a23ff169bc3..b13e993543a5 100644
--- a/src/main/webapp/app/exercises/programming/manage/services/programming-exercise-participation.service.ts
+++ b/src/main/webapp/app/exercises/programming/manage/services/programming-exercise-participation.service.ts
@@ -147,9 +147,9 @@ export class ProgrammingExerciseParticipationService implements IProgrammingExer
}
/**
- * Get the repository files with content for a given participation id at a specific commit hash.
- * The current user needs to be at least a instructor in the course of the participation.
- * @param participationId of the participation to get the commit infos for
+ * Get the vcs access log for a given participation id.
+ * The current user needs to be at least an instructor in the course of the participation.
+ * @param participationId of the participation to get the vcs Access log
*/
getVcsAccessLogForParticipation(participationId: number): Observable {
return this.http
@@ -157,7 +157,13 @@ export class ProgrammingExerciseParticipationService implements IProgrammingExer
.pipe(map((res: HttpResponse) => res.body ?? undefined));
}
- getVcsAccessLogForRepository(exerciseId: number, repositoryType: string) {
+ /**
+ * Get the vcs access log for a given exercise id and the repository type.
+ * The current user needs to be at least a instructor in the course of the participation.
+ * @param exerciseId of the exercise to get the vcs Access log
+ * @param repositoryType of the repository of the exercise, to get the vcs Access log
+ */
+ getVcsAccessLogForRepository(exerciseId: number, repositoryType: string): Observable {
const params: { [key: string]: number | string } = {};
if (repositoryType) {
params['repositoryType'] = repositoryType;
From d3ca8e79b9b68660edbc2c2d045904316b53adc4 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Tue, 3 Sep 2024 16:33:28 +0200
Subject: [PATCH 34/50] only enable features when using LocalVc
---
.../repository/VcsAccessLogRepository.java | 4 ++--
.../artemis/service/VcsAccessLogService.java | 4 ++--
.../localvc/LocalVCServletService.java | 12 +++++++-----
...grammingExerciseParticipationResource.java | 19 ++++++++++++++-----
.../repository-view.component.html | 2 +-
.../repository-view.component.ts | 7 +++++++
6 files changed, 33 insertions(+), 15 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
index 3dcb8f912e5c..79c7f95efc29 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
@@ -1,6 +1,6 @@
package de.tum.in.www1.artemis.repository;
-import static de.tum.in.www1.artemis.config.Constants.PROFILE_CORE;
+import static de.tum.in.www1.artemis.config.Constants.PROFILE_LOCALVC;
import java.time.ZonedDateTime;
import java.util.List;
@@ -22,7 +22,7 @@
* If you don't need deleted user entities, add `WHERE user.isDeleted = FALSE` to your query.
*
*/
-@Profile(PROFILE_CORE)
+@Profile(PROFILE_LOCALVC)
@Repository
public interface VcsAccessLogRepository extends ArtemisJpaRepository {
diff --git a/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java b/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
index 465afe7cd059..839b13582a7f 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
@@ -1,6 +1,6 @@
package de.tum.in.www1.artemis.service;
-import static de.tum.in.www1.artemis.config.Constants.PROFILE_CORE;
+import static de.tum.in.www1.artemis.config.Constants.PROFILE_LOCALVC;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
@@ -19,7 +19,7 @@
import de.tum.in.www1.artemis.repository.VcsAccessLogRepository;
import de.tum.in.www1.artemis.web.rest.repository.RepositoryActionType;
-@Profile(PROFILE_CORE)
+@Profile(PROFILE_LOCALVC)
@Service
public class VcsAccessLogService {
diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java
index 5546ac2ae982..2153ac662e06 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCServletService.java
@@ -102,7 +102,8 @@ public class LocalVCServletService {
private final ProgrammingTriggerService programmingTriggerService;
- private final VcsAccessLogService vcsAccessLogService;
+ // TODO As soon as only LocalVC is supported, this Optional can be removed
+ private final Optional vcsAccessLogService;
private static URL localVCBaseUrl;
@@ -136,7 +137,7 @@ public LocalVCServletService(AuthenticationManager authenticationManager, UserRe
ProgrammingExerciseParticipationService programmingExerciseParticipationService, AuxiliaryRepositoryService auxiliaryRepositoryService,
ContinuousIntegrationTriggerService ciTriggerService, ProgrammingSubmissionService programmingSubmissionService,
ProgrammingMessagingService programmingMessagingService, ProgrammingTriggerService programmingTriggerService,
- ParticipationVCSAccessTokenRepository participationVCSAccessTokenRepository, VcsAccessLogService vcsAccessLogService) {
+ ParticipationVCSAccessTokenRepository participationVCSAccessTokenRepository, Optional vcsAccessLogService) {
this.authenticationManager = authenticationManager;
this.userRepository = userRepository;
this.programmingExerciseRepository = programmingExerciseRepository;
@@ -451,8 +452,8 @@ public void authorizeUser(String repositoryTypeOrUserName, User user, Programmin
log.warn("Failed to obtain commit hash for repository {}. Error: {}", localVCRepositoryUri.getRelativeRepositoryPath().toString(), e.getMessage());
}
// Write a access log entry to the database
- vcsAccessLogService.storeAccessLog(user, participation, repositoryActionType, authenticationMechanism, commitHash, ipAddress);
-
+ String finalCommitHash = commitHash;
+ vcsAccessLogService.ifPresent(service -> service.storeAccessLog(user, participation, repositoryActionType, authenticationMechanism, finalCommitHash, ipAddress));
}
/**
@@ -521,7 +522,8 @@ public void processNewPush(String commitHash, Repository repository) {
processNewPushToRepository(participation, commit);
// For push the correct commitHash is only available here, therefore the preliminary null value is overwritten
- vcsAccessLogService.updateCommitHash(participation, commitHash);
+ String finalCommitHash = commitHash;
+ vcsAccessLogService.ifPresent(service -> service.updateCommitHash(participation, finalCommitHash));
}
catch (GitAPIException | IOException e) {
// This catch clause does not catch exceptions that happen during runBuildJob() as that method is called asynchronously.
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
index 2f681685a870..2c05fb5e7cb6 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
@@ -46,6 +46,7 @@
import de.tum.in.www1.artemis.service.AuthorizationCheckService;
import de.tum.in.www1.artemis.service.ParticipationAuthorizationCheckService;
import de.tum.in.www1.artemis.service.ResultService;
+import de.tum.in.www1.artemis.service.VcsAccessLogService;
import de.tum.in.www1.artemis.service.exam.ExamService;
import de.tum.in.www1.artemis.service.programming.ProgrammingExerciseParticipationService;
import de.tum.in.www1.artemis.service.programming.ProgrammingSubmissionService;
@@ -87,13 +88,15 @@ public class ProgrammingExerciseParticipationResource {
private final StudentExamRepository studentExamRepository;
- private final VcsAccessLogRepository vcsAccessLogRepository;
+ private final Optional vcsAccessLogRepository;
+
+ private final VcsAccessLogService vcsAccessLogService;
public ProgrammingExerciseParticipationResource(ProgrammingExerciseParticipationService programmingExerciseParticipationService, ResultRepository resultRepository,
ParticipationRepository participationRepository, ProgrammingExerciseStudentParticipationRepository programmingExerciseStudentParticipationRepository,
ProgrammingSubmissionService submissionService, ProgrammingExerciseRepository programmingExerciseRepository, AuthorizationCheckService authCheckService,
ResultService resultService, ParticipationAuthorizationCheckService participationAuthCheckService, RepositoryService repositoryService,
- StudentExamRepository studentExamRepository, VcsAccessLogRepository vcsAccessLogRepository) {
+ StudentExamRepository studentExamRepository, Optional vcsAccessLogRepository, VcsAccessLogService vcsAccessLogService) {
this.programmingExerciseParticipationService = programmingExerciseParticipationService;
this.participationRepository = participationRepository;
this.programmingExerciseStudentParticipationRepository = programmingExerciseStudentParticipationRepository;
@@ -106,6 +109,7 @@ public ProgrammingExerciseParticipationResource(ProgrammingExerciseParticipation
this.repositoryService = repositoryService;
this.studentExamRepository = studentExamRepository;
this.vcsAccessLogRepository = vcsAccessLogRepository;
+ this.vcsAccessLogService = vcsAccessLogService;
}
/**
@@ -326,10 +330,13 @@ public ResponseEntity> getCommitHistoryForParticipationRepo(
@GetMapping("programming-exercise-participations/{participationId}/vcs-access-log")
@EnforceAtLeastInstructor
public ResponseEntity> getVcsAccessLogForParticipationRepo(@PathVariable long participationId) {
+ if (vcsAccessLogRepository.isEmpty()) {
+ return ResponseEntity.notFound().build();
+ }
ProgrammingExerciseStudentParticipation participation = programmingExerciseStudentParticipationRepository.findByIdElseThrow(participationId);
participationAuthCheckService.checkCanAccessParticipationElseThrow(participation);
log.info("Fetching VCS access logs for participation ID: {}", participationId);
- List vcsAccessLogs = vcsAccessLogRepository.findAllByParticipationId(participationId);
+ List vcsAccessLogs = vcsAccessLogRepository.get().findAllByParticipationId(participationId);
var vcsAccessLogDTOs = vcsAccessLogs.stream().map(VcsAccessLogDTO::of).toList();
return ResponseEntity.ok(vcsAccessLogDTOs);
}
@@ -433,7 +440,9 @@ else if (repositoryType != null) {
@GetMapping("programming-exercise/{exerciseId}/vcs-access-log/{repositoryType}")
@EnforceAtLeastEditor
public ResponseEntity> getVcsAccessLogForExerciseRepository(@PathVariable long exerciseId, @PathVariable RepositoryType repositoryType) {
-
+ if (vcsAccessLogRepository.isEmpty()) {
+ return ResponseEntity.notFound().build();
+ }
if (repositoryType != RepositoryType.TEMPLATE && repositoryType != RepositoryType.SOLUTION) {
throw new BadRequestAlertException("Can only get vcs access log from template and assignment repositories", ENTITY_NAME, "incorrect repositoryType");
}
@@ -443,7 +452,7 @@ public ResponseEntity> getVcsAccessLogForExerciseRepositor
var participation = repositoryType == RepositoryType.TEMPLATE ? programmingExercise.getTemplateParticipation() : programmingExercise.getSolutionParticipation();
- List vcsAccessLogs = vcsAccessLogRepository.findAllByParticipationId(participation.getId());
+ List vcsAccessLogs = vcsAccessLogRepository.get().findAllByParticipationId(participation.getId());
var vcsAccessLogDTOs = vcsAccessLogs.stream().map(VcsAccessLogDTO::of).toList();
return ResponseEntity.ok(vcsAccessLogDTOs);
}
diff --git a/src/main/webapp/app/localvc/repository-view/repository-view.component.html b/src/main/webapp/app/localvc/repository-view/repository-view.component.html
index 0318f6105969..6a41335a4f51 100644
--- a/src/main/webapp/app/localvc/repository-view/repository-view.component.html
+++ b/src/main/webapp/app/localvc/repository-view/repository-view.component.html
@@ -34,7 +34,7 @@
}
- @if (vcsAccessLogRoute && enableVcsAccessLog && allowVcsAccessLog) {
+ @if (vcsAccessLogRoute && enableVcsAccessLog && allowVcsAccessLog && localVcEnabled) {
diff --git a/src/main/webapp/app/localvc/repository-view/repository-view.component.ts b/src/main/webapp/app/localvc/repository-view/repository-view.component.ts
index 138d8809aa15..72e9bbdfc3ad 100644
--- a/src/main/webapp/app/localvc/repository-view/repository-view.component.ts
+++ b/src/main/webapp/app/localvc/repository-view/repository-view.component.ts
@@ -16,6 +16,8 @@ import { faClockRotateLeft } from '@fortawesome/free-solid-svg-icons';
import { ProgrammingExerciseInstructorRepositoryType, ProgrammingExerciseService } from 'app/exercises/programming/manage/services/programming-exercise.service';
import { ButtonSize, ButtonType } from 'app/shared/components/button.component';
import { Feedback } from 'app/entities/feedback.model';
+import { PROFILE_LOCALVC } from 'app/app.constants';
+import { ProfileService } from 'app/shared/layouts/profiles/profile.service';
@Component({
selector: 'jhi-repository-view',
@@ -48,6 +50,7 @@ export class RepositoryViewComponent implements OnInit, OnDestroy {
result: Result;
resultHasInlineFeedback = false;
showInlineFeedback = false;
+ localVcEnabled = false;
faClockRotateLeft = faClockRotateLeft;
participationWithLatestResultSub: Subscription;
@@ -57,6 +60,7 @@ export class RepositoryViewComponent implements OnInit, OnDestroy {
private accountService: AccountService,
public domainService: DomainService,
private route: ActivatedRoute,
+ private profileService: ProfileService,
private programmingExerciseParticipationService: ProgrammingExerciseParticipationService,
private programmingExerciseService: ProgrammingExerciseService,
private router: Router,
@@ -96,6 +100,9 @@ export class RepositoryViewComponent implements OnInit, OnDestroy {
this.loadDifferentParticipation(this.repositoryType, exerciseId);
}
});
+ this.profileService.getProfileInfo().subscribe((profileInfo) => {
+ this.localVcEnabled = profileInfo.activeProfiles.includes(PROFILE_LOCALVC);
+ });
}
/**
From 292293a7557dbc8900a1fa6741d0ab440f868bc1 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Tue, 3 Sep 2024 16:42:19 +0200
Subject: [PATCH 35/50] moved tests
---
...gExerciseParticipationIntegrationTest.java | 26 ---------
...VCLocalCIParticipationIntegrationTest.java | 53 +++++++++++++++++--
2 files changed, 50 insertions(+), 29 deletions(-)
diff --git a/src/test/java/de/tum/in/www1/artemis/exercise/programming/ProgrammingExerciseParticipationIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/exercise/programming/ProgrammingExerciseParticipationIntegrationTest.java
index 9d0ffa1ad2d8..7fe9c867d5d8 100644
--- a/src/test/java/de/tum/in/www1/artemis/exercise/programming/ProgrammingExerciseParticipationIntegrationTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/exercise/programming/ProgrammingExerciseParticipationIntegrationTest.java
@@ -37,8 +37,6 @@
import de.tum.in.www1.artemis.domain.participation.SolutionProgrammingExerciseParticipation;
import de.tum.in.www1.artemis.domain.participation.StudentParticipation;
import de.tum.in.www1.artemis.domain.participation.TemplateProgrammingExerciseParticipation;
-import de.tum.in.www1.artemis.domain.vcstokens.AuthenticationMechanism;
-import de.tum.in.www1.artemis.domain.vcstokens.VcsAccessLog;
import de.tum.in.www1.artemis.participation.ParticipationUtilService;
import de.tum.in.www1.artemis.repository.ParticipationRepository;
import de.tum.in.www1.artemis.repository.ProgrammingExerciseRepository;
@@ -46,8 +44,6 @@
import de.tum.in.www1.artemis.repository.StudentParticipationRepository;
import de.tum.in.www1.artemis.repository.VcsAccessLogRepository;
import de.tum.in.www1.artemis.web.rest.dto.CommitInfoDTO;
-import de.tum.in.www1.artemis.web.rest.dto.VcsAccessLogDTO;
-import de.tum.in.www1.artemis.web.rest.repository.RepositoryActionType;
class ProgrammingExerciseParticipationIntegrationTest extends AbstractSpringIntegrationIndependentTest {
@@ -763,28 +759,6 @@ void retrieveCommitHistoryTutorNotOwningParticipationSuccess() throws Exception
request.getList("/api/programming-exercise-participations/" + participation.getId() + "/commit-history", HttpStatus.OK, CommitInfoDTO.class);
}
- @Test
- @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR")
- void testGetVcsAccessLog() throws Exception {
- var participation = participationUtilService.addStudentParticipationForProgrammingExercise(programmingExercise, TEST_PREFIX + "instructor1");
- var user = userRepository.getUser();
- vcsAccessLogRepository.save(new VcsAccessLog(user, participation, "instructor", "instructorMail@mail.de", RepositoryActionType.READ, AuthenticationMechanism.SSH, "", ""));
- var li = request.getList(participationsBaseUrl + participation.getId() + "/vcs-access-log", HttpStatus.OK, VcsAccessLogDTO.class);
- assertThat(li.size()).isEqualTo(1);
- assertThat(li.getFirst().userId()).isEqualTo(user.getId());
- }
-
- @Test
- @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR")
- void testGetVcsAccessLogOfTemplateParticipation() throws Exception {
- var user = userRepository.getUser();
- vcsAccessLogRepository.save(new VcsAccessLog(user, programmingExercise.getTemplateParticipation(), "instructor", "instructorMail@mail.de", RepositoryActionType.READ,
- AuthenticationMechanism.SSH, "", ""));
- var li = request.getList("/api/programming-exercise/" + programmingExercise.getId() + "/vcs-access-log/TEMPLATE", HttpStatus.OK, VcsAccessLogDTO.class);
- assertThat(li.size()).isEqualTo(1);
- assertThat(li.getFirst().userId()).isEqualTo(user.getId());
- }
-
private Result addStudentParticipationWithResult(AssessmentType assessmentType, ZonedDateTime completionDate) {
programmingExerciseParticipation = participationUtilService.addStudentParticipationForProgrammingExercise(programmingExercise, TEST_PREFIX + "student1");
Result r = participationUtilService.addResultToParticipation(assessmentType, completionDate, programmingExerciseParticipation);
diff --git a/src/test/java/de/tum/in/www1/artemis/localvcci/LocalVCLocalCIParticipationIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/localvcci/LocalVCLocalCIParticipationIntegrationTest.java
index cbd957d51dd1..c5da8ff922fc 100644
--- a/src/test/java/de/tum/in/www1/artemis/localvcci/LocalVCLocalCIParticipationIntegrationTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/localvcci/LocalVCLocalCIParticipationIntegrationTest.java
@@ -4,6 +4,7 @@
import java.time.ZonedDateTime;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
@@ -15,9 +16,16 @@
import de.tum.in.www1.artemis.domain.User;
import de.tum.in.www1.artemis.domain.participation.StudentParticipation;
import de.tum.in.www1.artemis.domain.participation.TemplateProgrammingExerciseParticipation;
+import de.tum.in.www1.artemis.domain.vcstokens.AuthenticationMechanism;
+import de.tum.in.www1.artemis.domain.vcstokens.VcsAccessLog;
import de.tum.in.www1.artemis.exercise.programming.ProgrammingExerciseUtilService;
+import de.tum.in.www1.artemis.participation.ParticipationUtilService;
+import de.tum.in.www1.artemis.repository.ProgrammingExerciseStudentParticipationRepository;
+import de.tum.in.www1.artemis.repository.VcsAccessLogRepository;
import de.tum.in.www1.artemis.service.connectors.localvc.LocalVCRepositoryUri;
import de.tum.in.www1.artemis.util.LocalRepository;
+import de.tum.in.www1.artemis.web.rest.dto.VcsAccessLogDTO;
+import de.tum.in.www1.artemis.web.rest.repository.RepositoryActionType;
class LocalVCLocalCIParticipationIntegrationTest extends AbstractSpringIntegrationLocalCILocalVCTest {
@@ -26,12 +34,28 @@ class LocalVCLocalCIParticipationIntegrationTest extends AbstractSpringIntegrati
@Autowired
private ProgrammingExerciseUtilService programmingExerciseUtilService;
+ @Autowired
+ private VcsAccessLogRepository vcsAccessLogRepository;
+
+ @Autowired
+ private ProgrammingExerciseStudentParticipationRepository programmingExerciseStudentParticipationRepository;
+
+ @Autowired
+ private ParticipationUtilService participationUtilService;
+
+ private ProgrammingExercise programmingExercise;
+
+ @BeforeEach
+ void initTestCase() {
+ userUtilService.addUsers(TEST_PREFIX, 4, 2, 0, 2);
+ Course course = programmingExerciseUtilService.addCourseWithOneProgrammingExerciseAndTestCases();
+ programmingExercise = exerciseUtilService.getFirstExerciseWithType(course, ProgrammingExercise.class);
+ programmingExercise = programmingExerciseRepository.findWithEagerStudentParticipationsById(programmingExercise.getId()).orElseThrow();
+ }
+
@Test
@WithMockUser(username = TEST_PREFIX + "student1", roles = "USER")
void testStartParticipation() throws Exception {
- userUtilService.addUsers(TEST_PREFIX, 1, 0, 0, 0);
- Course course = programmingExerciseUtilService.addCourseWithOneProgrammingExercise();
- ProgrammingExercise programmingExercise = exerciseUtilService.getFirstExerciseWithType(course, ProgrammingExercise.class);
String projectKey = programmingExercise.getProjectKey();
programmingExercise.setStartDate(ZonedDateTime.now().minusHours(1));
// Set the branch to null to force the usage of LocalVCService#getDefaultBranchOfRepository().
@@ -63,4 +87,27 @@ void testStartParticipation() throws Exception {
templateRepository.resetLocalRepo();
}
+
+ @Test
+ @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR")
+ void testGetVcsAccessLog() throws Exception {
+ var participation = participationUtilService.addStudentParticipationForProgrammingExercise(programmingExercise, TEST_PREFIX + "instructor1");
+ var user = userRepository.getUser();
+ vcsAccessLogRepository.save(new VcsAccessLog(user, participation, "instructor", "instructorMail@mail.de", RepositoryActionType.READ, AuthenticationMechanism.SSH, "", ""));
+ var li = request.getList("/api/programming-exercise-participations/" + participation.getId() + "/vcs-access-log", HttpStatus.OK, VcsAccessLogDTO.class);
+ assertThat(li.size()).isEqualTo(1);
+ assertThat(li.getFirst().userId()).isEqualTo(user.getId());
+ }
+
+ @Test
+ @WithMockUser(username = TEST_PREFIX + "instructor1", roles = "INSTRUCTOR")
+ void testGetVcsAccessLogOfTemplateParticipation() throws Exception {
+ var user = userRepository.getUser();
+ vcsAccessLogRepository.save(new VcsAccessLog(user, programmingExercise.getTemplateParticipation(), "instructor", "instructorMail@mail.de", RepositoryActionType.READ,
+ AuthenticationMechanism.SSH, "", ""));
+ var li = request.getList("/api/programming-exercise/" + programmingExercise.getId() + "/vcs-access-log/TEMPLATE", HttpStatus.OK, VcsAccessLogDTO.class);
+ assertThat(li.size()).isEqualTo(1);
+ assertThat(li.getFirst().userId()).isEqualTo(user.getId());
+ }
+
}
From 8fd49bd6b4857d9832a094489816afc4c818c444 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Tue, 3 Sep 2024 17:03:11 +0200
Subject: [PATCH 36/50] moved tests
---
.../ProgrammingExerciseParticipationResource.java | 6 +-----
.../ProgrammingExerciseParticipationIntegrationTest.java | 4 ----
.../LocalVCLocalCIParticipationIntegrationTest.java | 8 +++-----
3 files changed, 4 insertions(+), 14 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
index 2c05fb5e7cb6..9f739af32e2a 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
@@ -46,7 +46,6 @@
import de.tum.in.www1.artemis.service.AuthorizationCheckService;
import de.tum.in.www1.artemis.service.ParticipationAuthorizationCheckService;
import de.tum.in.www1.artemis.service.ResultService;
-import de.tum.in.www1.artemis.service.VcsAccessLogService;
import de.tum.in.www1.artemis.service.exam.ExamService;
import de.tum.in.www1.artemis.service.programming.ProgrammingExerciseParticipationService;
import de.tum.in.www1.artemis.service.programming.ProgrammingSubmissionService;
@@ -90,13 +89,11 @@ public class ProgrammingExerciseParticipationResource {
private final Optional vcsAccessLogRepository;
- private final VcsAccessLogService vcsAccessLogService;
-
public ProgrammingExerciseParticipationResource(ProgrammingExerciseParticipationService programmingExerciseParticipationService, ResultRepository resultRepository,
ParticipationRepository participationRepository, ProgrammingExerciseStudentParticipationRepository programmingExerciseStudentParticipationRepository,
ProgrammingSubmissionService submissionService, ProgrammingExerciseRepository programmingExerciseRepository, AuthorizationCheckService authCheckService,
ResultService resultService, ParticipationAuthorizationCheckService participationAuthCheckService, RepositoryService repositoryService,
- StudentExamRepository studentExamRepository, Optional vcsAccessLogRepository, VcsAccessLogService vcsAccessLogService) {
+ StudentExamRepository studentExamRepository, Optional vcsAccessLogRepository) {
this.programmingExerciseParticipationService = programmingExerciseParticipationService;
this.participationRepository = participationRepository;
this.programmingExerciseStudentParticipationRepository = programmingExerciseStudentParticipationRepository;
@@ -109,7 +106,6 @@ public ProgrammingExerciseParticipationResource(ProgrammingExerciseParticipation
this.repositoryService = repositoryService;
this.studentExamRepository = studentExamRepository;
this.vcsAccessLogRepository = vcsAccessLogRepository;
- this.vcsAccessLogService = vcsAccessLogService;
}
/**
diff --git a/src/test/java/de/tum/in/www1/artemis/exercise/programming/ProgrammingExerciseParticipationIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/exercise/programming/ProgrammingExerciseParticipationIntegrationTest.java
index 7fe9c867d5d8..5c1f03224682 100644
--- a/src/test/java/de/tum/in/www1/artemis/exercise/programming/ProgrammingExerciseParticipationIntegrationTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/exercise/programming/ProgrammingExerciseParticipationIntegrationTest.java
@@ -42,7 +42,6 @@
import de.tum.in.www1.artemis.repository.ProgrammingExerciseRepository;
import de.tum.in.www1.artemis.repository.ProgrammingExerciseStudentParticipationRepository;
import de.tum.in.www1.artemis.repository.StudentParticipationRepository;
-import de.tum.in.www1.artemis.repository.VcsAccessLogRepository;
import de.tum.in.www1.artemis.web.rest.dto.CommitInfoDTO;
class ProgrammingExerciseParticipationIntegrationTest extends AbstractSpringIntegrationIndependentTest {
@@ -62,9 +61,6 @@ class ProgrammingExerciseParticipationIntegrationTest extends AbstractSpringInte
@Autowired
private ParticipationRepository participationRepository;
- @Autowired
- private VcsAccessLogRepository vcsAccessLogRepository;
-
@Autowired
private ProgrammingExerciseStudentParticipationRepository programmingExerciseStudentParticipationRepository;
diff --git a/src/test/java/de/tum/in/www1/artemis/localvcci/LocalVCLocalCIParticipationIntegrationTest.java b/src/test/java/de/tum/in/www1/artemis/localvcci/LocalVCLocalCIParticipationIntegrationTest.java
index c5da8ff922fc..2cad8992fedf 100644
--- a/src/test/java/de/tum/in/www1/artemis/localvcci/LocalVCLocalCIParticipationIntegrationTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/localvcci/LocalVCLocalCIParticipationIntegrationTest.java
@@ -20,7 +20,6 @@
import de.tum.in.www1.artemis.domain.vcstokens.VcsAccessLog;
import de.tum.in.www1.artemis.exercise.programming.ProgrammingExerciseUtilService;
import de.tum.in.www1.artemis.participation.ParticipationUtilService;
-import de.tum.in.www1.artemis.repository.ProgrammingExerciseStudentParticipationRepository;
import de.tum.in.www1.artemis.repository.VcsAccessLogRepository;
import de.tum.in.www1.artemis.service.connectors.localvc.LocalVCRepositoryUri;
import de.tum.in.www1.artemis.util.LocalRepository;
@@ -37,9 +36,6 @@ class LocalVCLocalCIParticipationIntegrationTest extends AbstractSpringIntegrati
@Autowired
private VcsAccessLogRepository vcsAccessLogRepository;
- @Autowired
- private ProgrammingExerciseStudentParticipationRepository programmingExerciseStudentParticipationRepository;
-
@Autowired
private ParticipationUtilService participationUtilService;
@@ -50,12 +46,14 @@ void initTestCase() {
userUtilService.addUsers(TEST_PREFIX, 4, 2, 0, 2);
Course course = programmingExerciseUtilService.addCourseWithOneProgrammingExerciseAndTestCases();
programmingExercise = exerciseUtilService.getFirstExerciseWithType(course, ProgrammingExercise.class);
- programmingExercise = programmingExerciseRepository.findWithEagerStudentParticipationsById(programmingExercise.getId()).orElseThrow();
}
@Test
@WithMockUser(username = TEST_PREFIX + "student1", roles = "USER")
void testStartParticipation() throws Exception {
+ userUtilService.addUsers(TEST_PREFIX, 1, 0, 0, 0);
+ Course course = programmingExerciseUtilService.addCourseWithOneProgrammingExercise();
+ ProgrammingExercise programmingExercise = exerciseUtilService.getFirstExerciseWithType(course, ProgrammingExercise.class);
String projectKey = programmingExercise.getProjectKey();
programmingExercise.setStartDate(ZonedDateTime.now().minusHours(1));
// Set the branch to null to force the usage of LocalVCService#getDefaultBranchOfRepository().
From e79ae43b0b4a817a17140443a3e693558e61b219 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Tue, 3 Sep 2024 17:13:06 +0200
Subject: [PATCH 37/50] improved test
---
.../localvc/vcs-repository-access-log-view.component.spec.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts b/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
index 961d3726c0dd..1b6657fe8a11 100644
--- a/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
+++ b/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
@@ -71,7 +71,7 @@ describe('VcsRepositoryAccessLogViewComponent', () => {
it('should load participation vcs access log', () => {
component.ngOnInit();
expect(component).toBeTruthy();
- expect(participationVcsAccessLogSpy).toHaveBeenCalled();
+ expect(participationVcsAccessLogSpy).toHaveBeenCalledOnce();
expect(component.vcsAccessLogEntries).toHaveLength(2);
expect(component.vcsAccessLogEntries[0].userId).toBe(userId);
});
From a617992377e96e19ac9b505d883ae980f4fa5b7a Mon Sep 17 00:00:00 2001
From: entholzer
Date: Fri, 6 Sep 2024 11:16:36 +0200
Subject: [PATCH 38/50] implemented feedback
---
.../artemis/service/programming/RepositoryService.java | 8 +++++---
.../scheduled/AutomaticVcsAccessLogCleanupService.java | 4 +---
.../ProgrammingExerciseParticipationResource.java | 3 +--
src/main/resources/config/application-prod.yml | 2 +-
.../vcs-repository-access-log-view.component.ts | 2 +-
src/main/webapp/i18n/en/repository.json | 2 +-
6 files changed, 10 insertions(+), 11 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/service/programming/RepositoryService.java b/src/main/java/de/tum/in/www1/artemis/service/programming/RepositoryService.java
index fb75d287d507..28049362d274 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/programming/RepositoryService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/programming/RepositoryService.java
@@ -61,11 +61,11 @@ public class RepositoryService {
private final ProfileService profileService;
- private final VcsAccessLogService vcsAccessLogService;
+ private final Optional vcsAccessLogService;
private static final Logger log = LoggerFactory.getLogger(RepositoryService.class);
- public RepositoryService(GitService gitService, ProfileService profileService, VcsAccessLogService vcsAccessLogService) {
+ public RepositoryService(GitService gitService, ProfileService profileService, Optional vcsAccessLogService) {
this.gitService = gitService;
this.profileService = profileService;
this.vcsAccessLogService = vcsAccessLogService;
@@ -479,7 +479,9 @@ public void pullChanges(Repository repository) {
public void commitChanges(Repository repository, User user, Long domainId) throws GitAPIException {
gitService.stageAllChanges(repository);
gitService.commitAndPush(repository, "Changes by Online Editor", true, user);
- vcsAccessLogService.storeCodeEditorAccessLog(repository, user, domainId);
+ if (vcsAccessLogService.isPresent()) {
+ vcsAccessLogService.get().storeCodeEditorAccessLog(repository, user, domainId);
+ }
}
/**
diff --git a/src/main/java/de/tum/in/www1/artemis/service/scheduled/AutomaticVcsAccessLogCleanupService.java b/src/main/java/de/tum/in/www1/artemis/service/scheduled/AutomaticVcsAccessLogCleanupService.java
index f650be7aca28..bdad97c2de98 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/scheduled/AutomaticVcsAccessLogCleanupService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/scheduled/AutomaticVcsAccessLogCleanupService.java
@@ -1,7 +1,5 @@
package de.tum.in.www1.artemis.service.scheduled;
-import static de.tum.in.www1.artemis.config.Constants.PROFILE_SCHEDULING;
-
import java.time.ZonedDateTime;
import org.slf4j.Logger;
@@ -14,7 +12,7 @@
import de.tum.in.www1.artemis.repository.VcsAccessLogRepository;
@Service
-@Profile(PROFILE_SCHEDULING)
+@Profile("scheduling & localvc")
public class AutomaticVcsAccessLogCleanupService {
private static final Logger log = LoggerFactory.getLogger(AutomaticVcsAccessLogCleanupService.class);
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
index 9f739af32e2a..45a89e6c3fac 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
@@ -39,7 +39,6 @@
import de.tum.in.www1.artemis.repository.StudentExamRepository;
import de.tum.in.www1.artemis.repository.VcsAccessLogRepository;
import de.tum.in.www1.artemis.security.Role;
-import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastEditor;
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastInstructor;
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastStudent;
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastTutor;
@@ -434,7 +433,7 @@ else if (repositoryType != null) {
* @throws BadRequestAlertException if the repository type is invalid
*/
@GetMapping("programming-exercise/{exerciseId}/vcs-access-log/{repositoryType}")
- @EnforceAtLeastEditor
+ @EnforceAtLeastInstructor
public ResponseEntity> getVcsAccessLogForExerciseRepository(@PathVariable long exerciseId, @PathVariable RepositoryType repositoryType) {
if (vcsAccessLogRepository.isEmpty()) {
return ResponseEntity.notFound().build();
diff --git a/src/main/resources/config/application-prod.yml b/src/main/resources/config/application-prod.yml
index 74105d146d2f..576e5ca01cde 100644
--- a/src/main/resources/config/application-prod.yml
+++ b/src/main/resources/config/application-prod.yml
@@ -109,7 +109,7 @@ jhipster:
port: 5000
ring-buffer-size: 512
audit-events:
- retention-period: 120 # Number of days before audit events and vsc access logs are deleted.
+ retention-period: 120 # Number of days before audit events and VCS access logs are deleted.
# Properties to be exposed on the /info management endpoint
info:
guided-tour:
diff --git a/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts b/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
index 7da4cd407a8e..0295ff602e32 100644
--- a/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
+++ b/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
@@ -57,7 +57,7 @@ export class VcsRepositoryAccessLogViewComponent implements OnInit, OnDestroy {
this.extractEntries(accessLogEntries);
}
- public loadVcsAccessLog(exerciseId: number, repositoryType: any) {
+ public loadVcsAccessLog(exerciseId: number, repositoryType: string) {
const accessLogEntries: Observable = this.programmingExerciseParticipationService.getVcsAccessLogForRepository(exerciseId, repositoryType);
this.extractEntries(accessLogEntries);
}
diff --git a/src/main/webapp/i18n/en/repository.json b/src/main/webapp/i18n/en/repository.json
index 67df637c83a9..8ed72d96e6ee 100644
--- a/src/main/webapp/i18n/en/repository.json
+++ b/src/main/webapp/i18n/en/repository.json
@@ -27,7 +27,7 @@
"userId": "User Id",
"author": "Author",
"actionType": "Action Type",
- "authMechanism": "Accesstype",
+ "authMechanism": "Access Type",
"commitHash": "Commit Hash",
"timeStamp": "Timestamp",
"error": "VCS Access log could not be retrieved"
From fd49c1e9dfe9f8d3b21af7762501e684929b4b4c Mon Sep 17 00:00:00 2001
From: entholzer
Date: Fri, 6 Sep 2024 12:54:42 +0200
Subject: [PATCH 39/50] removed redundant check
---
.../localvc/vcs-repository-access-log-view.component.spec.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts b/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
index 1b6657fe8a11..9ce7de0e6e12 100644
--- a/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
+++ b/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
@@ -70,7 +70,6 @@ describe('VcsRepositoryAccessLogViewComponent', () => {
it('should load participation vcs access log', () => {
component.ngOnInit();
- expect(component).toBeTruthy();
expect(participationVcsAccessLogSpy).toHaveBeenCalledOnce();
expect(component.vcsAccessLogEntries).toHaveLength(2);
expect(component.vcsAccessLogEntries[0].userId).toBe(userId);
From ffd25f2d0d8f47c85f2b31af550ca111cf340150 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Fri, 6 Sep 2024 12:59:59 +0200
Subject: [PATCH 40/50] mock profileService in test
---
.../localvc/vcs-repository-access-log-view.component.spec.ts | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts b/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
index 9ce7de0e6e12..ae3a3714d3a4 100644
--- a/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
+++ b/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
@@ -12,6 +12,8 @@ import { VcsAccessLogDTO } from 'app/entities/vcs-access-log-entry.model';
import { MockTranslateService } from '../../helpers/mocks/service/mock-translate.service';
import { AlertService } from 'app/core/util/alert.service';
import { MockAlertService } from '../../helpers/mocks/service/mock-alert.service';
+import { MockProfileService } from '../../helpers/mocks/service/mock-profile.service';
+import { ProfileService } from 'app/shared/layouts/profiles/profile.service';
describe('VcsRepositoryAccessLogViewComponent', () => {
let component: VcsRepositoryAccessLogViewComponent;
@@ -55,6 +57,7 @@ describe('VcsRepositoryAccessLogViewComponent', () => {
{ provide: ProgrammingExerciseService, useClass: MockProgrammingExerciseService },
{ provide: TranslateService, useClass: MockTranslateService },
{ provide: AlertService, useClass: MockAlertService },
+ { provide: ProfileService, useClass: MockProfileService },
],
})
.compileComponents()
From bf83d62843c483ba39509a17f09992949d9adfd0 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Fri, 6 Sep 2024 13:04:50 +0200
Subject: [PATCH 41/50] improved java docs
---
.../ProgrammingExerciseParticipationResource.java | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
index 45a89e6c3fac..e76aee321d66 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
@@ -320,13 +320,13 @@ public ResponseEntity> getCommitHistoryForParticipationRepo(
* Here we check if the user is least an instructor for the exercise. If true the user can have access to the vcs access log of any participation of the exercise.
*
* @param participationId the id of the participation for which to retrieve the vcs access log
- * @return the ResponseEntity with status 200 (OK) and with body containing a list of vcsAccessLogDTOs of the participation
+ * @return the ResponseEntity with status 200 (OK) and with body containing a list of vcsAccessLogDTOs of the participation, or 400 (Bad request) if localVC is not enabled.
*/
@GetMapping("programming-exercise-participations/{participationId}/vcs-access-log")
@EnforceAtLeastInstructor
public ResponseEntity> getVcsAccessLogForParticipationRepo(@PathVariable long participationId) {
if (vcsAccessLogRepository.isEmpty()) {
- return ResponseEntity.notFound().build();
+ return ResponseEntity.badRequest().build();
}
ProgrammingExerciseStudentParticipation participation = programmingExerciseStudentParticipationRepository.findByIdElseThrow(participationId);
participationAuthCheckService.checkCanAccessParticipationElseThrow(participation);
@@ -428,15 +428,14 @@ else if (repositoryType != null) {
*
* @param exerciseId the ID of the programming exercise
* @param repositoryType the type of repository (either TEMPLATE or SOLUTION) for which to retrieve the logs.
- * @return a {@link ResponseEntity} containing a list of {@link VcsAccessLogDTO} objects representing
- * the VCS access logs.
+ * @return the ResponseEntity with status 200 (OK) and with body containing a list of vcsAccessLogDTOs of the participation, or 400 (Bad request) if localVC is not enabled.
* @throws BadRequestAlertException if the repository type is invalid
*/
@GetMapping("programming-exercise/{exerciseId}/vcs-access-log/{repositoryType}")
@EnforceAtLeastInstructor
public ResponseEntity> getVcsAccessLogForExerciseRepository(@PathVariable long exerciseId, @PathVariable RepositoryType repositoryType) {
if (vcsAccessLogRepository.isEmpty()) {
- return ResponseEntity.notFound().build();
+ return ResponseEntity.badRequest().build();
}
if (repositoryType != RepositoryType.TEMPLATE && repositoryType != RepositoryType.SOLUTION) {
throw new BadRequestAlertException("Can only get vcs access log from template and assignment repositories", ENTITY_NAME, "incorrect repositoryType");
From df9372714c51a03baf185f72b27d0dd8e0a5011a Mon Sep 17 00:00:00 2001
From: entholzer
Date: Fri, 6 Sep 2024 13:08:46 +0200
Subject: [PATCH 42/50] improve Authorization
---
.../programming/ProgrammingExerciseParticipationResource.java | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java b/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
index e76aee321d66..9799d8687bfa 100644
--- a/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
+++ b/src/main/java/de/tum/in/www1/artemis/web/rest/programming/ProgrammingExerciseParticipationResource.java
@@ -42,6 +42,7 @@
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastInstructor;
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastStudent;
import de.tum.in.www1.artemis.security.annotations.EnforceAtLeastTutor;
+import de.tum.in.www1.artemis.security.annotations.enforceRoleInExercise.EnforceAtLeastInstructorInExercise;
import de.tum.in.www1.artemis.service.AuthorizationCheckService;
import de.tum.in.www1.artemis.service.ParticipationAuthorizationCheckService;
import de.tum.in.www1.artemis.service.ResultService;
@@ -432,7 +433,7 @@ else if (repositoryType != null) {
* @throws BadRequestAlertException if the repository type is invalid
*/
@GetMapping("programming-exercise/{exerciseId}/vcs-access-log/{repositoryType}")
- @EnforceAtLeastInstructor
+ @EnforceAtLeastInstructorInExercise
public ResponseEntity> getVcsAccessLogForExerciseRepository(@PathVariable long exerciseId, @PathVariable RepositoryType repositoryType) {
if (vcsAccessLogRepository.isEmpty()) {
return ResponseEntity.badRequest().build();
From b776d11679cdc8990c24f4def92d11420946d15b Mon Sep 17 00:00:00 2001
From: entholzer
Date: Fri, 6 Sep 2024 13:40:20 +0200
Subject: [PATCH 43/50] fix client test
---
.../localvc/repository-view.component.spec.ts | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/src/test/javascript/spec/component/localvc/repository-view.component.spec.ts b/src/test/javascript/spec/component/localvc/repository-view.component.spec.ts
index 5f4fae4014a9..ff004d03c91b 100644
--- a/src/test/javascript/spec/component/localvc/repository-view.component.spec.ts
+++ b/src/test/javascript/spec/component/localvc/repository-view.component.spec.ts
@@ -16,6 +16,8 @@ import { HttpResponse } from '@angular/common/http';
import { ProgrammingExercise } from 'app/entities/programming/programming-exercise.model';
import { DueDateStat } from 'app/course/dashboards/due-date-stat.model';
import { ProgrammingExerciseStudentParticipation } from 'app/entities/participation/programming-exercise-student-participation.model';
+import { ProfileService } from 'app/shared/layouts/profiles/profile.service';
+import { MockProfileService } from '../../helpers/mocks/service/mock-profile.service';
describe('RepositoryViewComponent', () => {
let component: RepositoryViewComponent;
@@ -39,6 +41,7 @@ describe('RepositoryViewComponent', () => {
{ provide: ProgrammingExerciseParticipationService, useClass: MockProgrammingExerciseParticipationService },
{ provide: ProgrammingExerciseService, useClass: MockProgrammingExerciseService },
{ provide: Router, useClass: MockRouter },
+ { provide: ProfileService, useClass: MockProfileService },
],
})
.compileComponents()
@@ -206,7 +209,16 @@ describe('RepositoryViewComponent', () => {
const mockParticipation: ProgrammingExerciseStudentParticipation = {
id: 2,
repositoryUri: 'student-repo-uri',
- exercise: { id: 1, numberOfAssessmentsOfCorrectionRounds: [new DueDateStat()], studentAssignedTeamIdComputed: true, secondCorrectionEnabled: true },
+ exercise: {
+ id: 1,
+ numberOfAssessmentsOfCorrectionRounds: [new DueDateStat()],
+ studentAssignedTeamIdComputed: true,
+ secondCorrectionEnabled: true,
+ course: {
+ instructorGroupName: 'instructorGroup',
+ isAtLeastInstructor: true,
+ },
+ },
results: [
{ id: 3, successful: true, score: 100, rated: true, hasComplaint: false, exampleResult: false, testCaseCount: 10, passedTestCaseCount: 10, codeIssueCount: 0 },
{ id: 4, successful: true, score: 100, rated: true, hasComplaint: false, exampleResult: false, testCaseCount: 10, passedTestCaseCount: 10, codeIssueCount: 0 },
From 24a6c26db609d11c0f61140cca0a9bb1c2e604de Mon Sep 17 00:00:00 2001
From: entholzer
Date: Fri, 6 Sep 2024 17:21:04 +0200
Subject: [PATCH 44/50] adapt query to only return one element
---
.../in/www1/artemis/repository/VcsAccessLogRepository.java | 4 +++-
.../de/tum/in/www1/artemis/service/VcsAccessLogService.java | 2 +-
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
index 79c7f95efc29..f71d949f878e 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/VcsAccessLogRepository.java
@@ -37,8 +37,10 @@ public interface VcsAccessLogRepository extends ArtemisJpaRepository findByParticipationIdWhereCommitHashIsNull(@Param("participationId") long participationId);
+ Optional findNewestByParticipationIdWhereCommitHashIsNull(@Param("participationId") long participationId);
/**
* Retrieves a list of {@link VcsAccessLog} entities associated with the specified participation ID.
diff --git a/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java b/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
index 839b13582a7f..12c59463a6b9 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/VcsAccessLogService.java
@@ -60,7 +60,7 @@ public void storeAccessLog(User user, ProgrammingExerciseParticipation participa
* @param commitHash The newest commit hash which should get set for the access log entry
*/
public void updateCommitHash(ProgrammingExerciseParticipation participation, String commitHash) {
- vcsAccessLogRepository.findByParticipationIdWhereCommitHashIsNull(participation.getId()).ifPresent(entry -> {
+ vcsAccessLogRepository.findNewestByParticipationIdWhereCommitHashIsNull(participation.getId()).ifPresent(entry -> {
entry.setCommitHash(commitHash);
vcsAccessLogRepository.save(entry);
});
From 3396c282364b6c99e991db32cf305e30f1ae53e9 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Mon, 9 Sep 2024 13:22:04 +0200
Subject: [PATCH 45/50] removed unnecessary ArtemisSharedModule import
---
.../vcs-repository-access-log-view.component.ts | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts b/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
index 0295ff602e32..53ff8427f489 100644
--- a/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
+++ b/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
@@ -8,13 +8,11 @@ import { ProgrammingExerciseParticipationService } from 'app/exercises/programmi
import { VcsAccessLogDTO } from 'app/entities/vcs-access-log-entry.model';
import { HttpErrorResponse } from '@angular/common/http';
import { AlertService } from 'app/core/util/alert.service';
-import { ArtemisSharedCommonModule } from 'app/shared/shared-common.module';
@Component({
selector: 'jhi-vcs-repository-access-log-view',
templateUrl: './vcs-repository-access-log-view.component.html',
standalone: true,
- imports: [ArtemisSharedCommonModule],
})
export class VcsRepositoryAccessLogViewComponent implements OnInit, OnDestroy {
participationId: number;
From b05008f3778892c4285b032129edc47fcbd52d9b Mon Sep 17 00:00:00 2001
From: entholzer
Date: Mon, 9 Sep 2024 16:04:01 +0200
Subject: [PATCH 46/50] refactored for new client guidelines
---
...-repository-access-log-view.component.html | 2 +-
...cs-repository-access-log-view.component.ts | 101 ++++++++----------
2 files changed, 45 insertions(+), 58 deletions(-)
diff --git a/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.html b/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.html
index 0d519e6d4cb5..32f6eaaf0da6 100644
--- a/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.html
+++ b/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.html
@@ -17,7 +17,7 @@
- @for (entry of vcsAccessLogEntries; track entry; let i = $index) {
+ @for (entry of vcsAccessLogEntries(); track entry; let i = $index) {
{{ i }} |
{{ entry.userId }} |
diff --git a/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts b/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
index 53ff8427f489..030e402938e4 100644
--- a/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
+++ b/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
@@ -1,77 +1,64 @@
-import { Component, OnDestroy, OnInit } from '@angular/core';
-import { Observable, Subject, Subscription } from 'rxjs';
-import { ActivatedRoute, Router } from '@angular/router';
-import { DomainService } from 'app/exercises/programming/shared/code-editor/service/code-editor-domain.service';
-import { ProgrammingExercise } from 'app/entities/programming/programming-exercise.model';
-import { ProgrammingExerciseStudentParticipation } from 'app/entities/participation/programming-exercise-student-participation.model';
+import { Component, computed, effect, inject, signal } from '@angular/core';
+import { Observable, lastValueFrom } from 'rxjs';
+import { ActivatedRoute } from '@angular/router';
import { ProgrammingExerciseParticipationService } from 'app/exercises/programming/manage/services/programming-exercise-participation.service';
import { VcsAccessLogDTO } from 'app/entities/vcs-access-log-entry.model';
-import { HttpErrorResponse } from '@angular/common/http';
import { AlertService } from 'app/core/util/alert.service';
+import { toSignal } from '@angular/core/rxjs-interop';
@Component({
selector: 'jhi-vcs-repository-access-log-view',
templateUrl: './vcs-repository-access-log-view.component.html',
standalone: true,
})
-export class VcsRepositoryAccessLogViewComponent implements OnInit, OnDestroy {
- participationId: number;
- vcsAccessLogEntries: VcsAccessLogDTO[];
- protected dialogErrorSource = new Subject();
+export class VcsRepositoryAccessLogViewComponent {
+ private readonly route = inject(ActivatedRoute);
+ private readonly programmingExerciseParticipationService = inject(ProgrammingExerciseParticipationService);
+ private readonly alertService = inject(AlertService);
- paramSub: Subscription;
- routeVcsAccessLog: string;
- participation: ProgrammingExerciseStudentParticipation;
- exercise: ProgrammingExercise;
+ protected readonly vcsAccessLogEntries = signal([]);
- constructor(
- public domainService: DomainService,
- private route: ActivatedRoute,
- private programmingExerciseParticipationService: ProgrammingExerciseParticipationService,
- private router: Router,
- private alertService: AlertService,
- ) {}
+ private readonly params = toSignal(this.route.params, { requireSync: true });
+ private readonly participationId = computed(() => {
+ const participationId = this.params().participationId;
+ if (participationId) {
+ return Number(participationId);
+ }
+ return undefined;
+ });
+ private readonly exerciseId = computed(() => Number(this.params().exerciseId));
+ private readonly repositoryType = computed(() => String(this.params().repositoryType));
- ngOnInit(): void {
- this.routeVcsAccessLog = this.router.url + '/vcs-access-log';
- this.paramSub = this.route.params.subscribe((params) => {
- const participationId = Number(params['participationId']);
- const exerciseId = Number(params['exerciseId']);
- const repositoryType = params['repositoryType'];
- if (participationId) {
- this.loadVcsAccessLogForParticipation(participationId);
- } else {
- this.loadVcsAccessLog(exerciseId, repositoryType);
- }
- });
- }
-
- ngOnDestroy(): void {
- this.paramSub.unsubscribe();
+ constructor() {
+ effect(
+ async () => {
+ if (this.participationId()) {
+ await this.loadVcsAccessLogForParticipation(this.participationId()!);
+ } else {
+ await this.loadVcsAccessLog(this.exerciseId(), this.repositoryType());
+ }
+ },
+ { allowSignalWrites: true },
+ );
}
- public loadVcsAccessLogForParticipation(participationId: number) {
- const accessLogEntries: Observable = this.programmingExerciseParticipationService.getVcsAccessLogForParticipation(participationId);
- this.extractEntries(accessLogEntries);
+ public async loadVcsAccessLogForParticipation(participationId: number) {
+ await this.extractEntries(() => this.programmingExerciseParticipationService.getVcsAccessLogForParticipation(participationId));
}
- public loadVcsAccessLog(exerciseId: number, repositoryType: string) {
- const accessLogEntries: Observable = this.programmingExerciseParticipationService.getVcsAccessLogForRepository(exerciseId, repositoryType);
- this.extractEntries(accessLogEntries);
+ public async loadVcsAccessLog(exerciseId: number, repositoryType: string) {
+ await this.extractEntries(() => this.programmingExerciseParticipationService.getVcsAccessLogForRepository(exerciseId, repositoryType));
}
- private extractEntries(accessLogEntries: Observable) {
- accessLogEntries.subscribe({
- next: (next: VcsAccessLogDTO[] | undefined) => {
- if (next) {
- this.vcsAccessLogEntries = next;
- this.dialogErrorSource.next('');
- }
- },
- error: (error: HttpErrorResponse) => {
- this.dialogErrorSource.next(error.message);
- this.alertService.error('artemisApp.repository.vcsAccessLog.error');
- },
- });
+ private async extractEntries(fun: () => Observable) {
+ try {
+ const accessLogEntries = await lastValueFrom(fun());
+ if (accessLogEntries) {
+ console.log(accessLogEntries);
+ this.vcsAccessLogEntries.set(accessLogEntries);
+ }
+ } catch (error) {
+ this.alertService.error('artemisApp.repository.vcsAccessLog.error');
+ }
}
}
From 0b2b6c09f55c2ae237aa4a0d4666341cadec35f3 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Mon, 9 Sep 2024 22:47:30 +0200
Subject: [PATCH 47/50] change client to new client guidelines
---
...cs-repository-access-log-view.component.ts | 3 +-
...pository-access-log-view.component.spec.ts | 33 +++++++++----------
2 files changed, 17 insertions(+), 19 deletions(-)
diff --git a/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts b/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
index 030e402938e4..acd06891bba5 100644
--- a/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
+++ b/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
@@ -16,7 +16,7 @@ export class VcsRepositoryAccessLogViewComponent {
private readonly programmingExerciseParticipationService = inject(ProgrammingExerciseParticipationService);
private readonly alertService = inject(AlertService);
- protected readonly vcsAccessLogEntries = signal([]);
+ private readonly vcsAccessLogEntries = signal([]);
private readonly params = toSignal(this.route.params, { requireSync: true });
private readonly participationId = computed(() => {
@@ -54,7 +54,6 @@ export class VcsRepositoryAccessLogViewComponent {
try {
const accessLogEntries = await lastValueFrom(fun());
if (accessLogEntries) {
- console.log(accessLogEntries);
this.vcsAccessLogEntries.set(accessLogEntries);
}
} catch (error) {
diff --git a/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts b/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
index ae3a3714d3a4..34b36b7afadc 100644
--- a/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
+++ b/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
@@ -16,7 +16,6 @@ import { MockProfileService } from '../../helpers/mocks/service/mock-profile.ser
import { ProfileService } from 'app/shared/layouts/profiles/profile.service';
describe('VcsRepositoryAccessLogViewComponent', () => {
- let component: VcsRepositoryAccessLogViewComponent;
let fixture: ComponentFixture;
let programmingExerciseParticipationService: ProgrammingExerciseParticipationService;
const userId = 4;
@@ -59,30 +58,30 @@ describe('VcsRepositoryAccessLogViewComponent', () => {
{ provide: AlertService, useClass: MockAlertService },
{ provide: ProfileService, useClass: MockProfileService },
],
- })
- .compileComponents()
- .then(() => {
- fixture = TestBed.createComponent(VcsRepositoryAccessLogViewComponent);
- component = fixture.componentInstance;
- programmingExerciseParticipationService = fixture.debugElement.injector.get(ProgrammingExerciseParticipationService);
-
- repositoryVcsAccessLogSpy = jest.spyOn(programmingExerciseParticipationService, 'getVcsAccessLogForRepository').mockReturnValue(of(mockVcsAccessLog));
- participationVcsAccessLogSpy = jest.spyOn(programmingExerciseParticipationService, 'getVcsAccessLogForParticipation').mockReturnValue(of(mockVcsAccessLog));
- });
+ }).compileComponents();
});
it('should load participation vcs access log', () => {
- component.ngOnInit();
+ setupTestBed();
+ fixture.detectChanges();
+
expect(participationVcsAccessLogSpy).toHaveBeenCalledOnce();
- expect(component.vcsAccessLogEntries).toHaveLength(2);
- expect(component.vcsAccessLogEntries[0].userId).toBe(userId);
});
it('should load template repository vcs access log', () => {
route.params = of({ exerciseId: '10', repositoryType: 'TEMPLATE' });
- component.ngOnInit();
+ TestBed.overrideProvider(ActivatedRoute, { useValue: route });
+
+ setupTestBed();
+ fixture.detectChanges();
+
expect(repositoryVcsAccessLogSpy).toHaveBeenCalledOnce();
- expect(component.vcsAccessLogEntries).toHaveLength(2);
- expect(component.vcsAccessLogEntries[0].userId).toBe(userId);
});
+
+ function setupTestBed() {
+ fixture = TestBed.createComponent(VcsRepositoryAccessLogViewComponent);
+ programmingExerciseParticipationService = fixture.debugElement.injector.get(ProgrammingExerciseParticipationService);
+ repositoryVcsAccessLogSpy = jest.spyOn(programmingExerciseParticipationService, 'getVcsAccessLogForRepository').mockReturnValue(of(mockVcsAccessLog));
+ participationVcsAccessLogSpy = jest.spyOn(programmingExerciseParticipationService, 'getVcsAccessLogForParticipation').mockReturnValue(of(mockVcsAccessLog));
+ }
});
From 63d9566c27dff42fd040edafea83bd4139076089 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Tue, 10 Sep 2024 20:09:16 +0200
Subject: [PATCH 48/50] moved test setup to beginning
---
...cs-repository-access-log-view.component.spec.ts | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts b/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
index 34b36b7afadc..44d03a38d661 100644
--- a/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
+++ b/src/test/javascript/spec/component/localvc/vcs-repository-access-log-view.component.spec.ts
@@ -47,6 +47,13 @@ describe('VcsRepositoryAccessLogViewComponent', () => {
const route = { params: of({ participationId: '5' }) } as any as ActivatedRoute;
+ function setupTestBed() {
+ fixture = TestBed.createComponent(VcsRepositoryAccessLogViewComponent);
+ programmingExerciseParticipationService = fixture.debugElement.injector.get(ProgrammingExerciseParticipationService);
+ repositoryVcsAccessLogSpy = jest.spyOn(programmingExerciseParticipationService, 'getVcsAccessLogForRepository').mockReturnValue(of(mockVcsAccessLog));
+ participationVcsAccessLogSpy = jest.spyOn(programmingExerciseParticipationService, 'getVcsAccessLogForParticipation').mockReturnValue(of(mockVcsAccessLog));
+ }
+
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [VcsRepositoryAccessLogViewComponent],
@@ -77,11 +84,4 @@ describe('VcsRepositoryAccessLogViewComponent', () => {
expect(repositoryVcsAccessLogSpy).toHaveBeenCalledOnce();
});
-
- function setupTestBed() {
- fixture = TestBed.createComponent(VcsRepositoryAccessLogViewComponent);
- programmingExerciseParticipationService = fixture.debugElement.injector.get(ProgrammingExerciseParticipationService);
- repositoryVcsAccessLogSpy = jest.spyOn(programmingExerciseParticipationService, 'getVcsAccessLogForRepository').mockReturnValue(of(mockVcsAccessLog));
- participationVcsAccessLogSpy = jest.spyOn(programmingExerciseParticipationService, 'getVcsAccessLogForParticipation').mockReturnValue(of(mockVcsAccessLog));
- }
});
From 160e6f7b990a8fbd9ca442435ae137ba61493b71 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Tue, 10 Sep 2024 20:18:04 +0200
Subject: [PATCH 49/50] add translate directive
---
.../programming-exercise-participation.service.ts | 1 -
.../vcs-repository-access-log-view.component.ts | 8 +++++---
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/main/webapp/app/exercises/programming/manage/services/programming-exercise-participation.service.ts b/src/main/webapp/app/exercises/programming/manage/services/programming-exercise-participation.service.ts
index 1ca9879c4cab..e83414219bb6 100644
--- a/src/main/webapp/app/exercises/programming/manage/services/programming-exercise-participation.service.ts
+++ b/src/main/webapp/app/exercises/programming/manage/services/programming-exercise-participation.service.ts
@@ -8,7 +8,6 @@ import { Result } from 'app/entities/result.model';
import { EntityTitleService, EntityType } from 'app/shared/layouts/navbar/entity-title.service';
import { createRequestOption } from 'app/shared/util/request.util';
import { Observable, map, tap } from 'rxjs';
-import { CommitInfo } from 'app/entities/programming/programming-submission.model';
import { VcsAccessLogDTO } from 'app/entities/vcs-access-log-entry.model';
export interface IProgrammingExerciseParticipationService {
diff --git a/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts b/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
index acd06891bba5..0f72191f8405 100644
--- a/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
+++ b/src/main/webapp/app/localvc/vcs-repository-access-log-view/vcs-repository-access-log-view.component.ts
@@ -5,18 +5,20 @@ import { ProgrammingExerciseParticipationService } from 'app/exercises/programmi
import { VcsAccessLogDTO } from 'app/entities/vcs-access-log-entry.model';
import { AlertService } from 'app/core/util/alert.service';
import { toSignal } from '@angular/core/rxjs-interop';
+import { TranslateDirective } from 'app/shared/language/translate.directive';
@Component({
selector: 'jhi-vcs-repository-access-log-view',
templateUrl: './vcs-repository-access-log-view.component.html',
standalone: true,
+ imports: [TranslateDirective],
})
export class VcsRepositoryAccessLogViewComponent {
private readonly route = inject(ActivatedRoute);
private readonly programmingExerciseParticipationService = inject(ProgrammingExerciseParticipationService);
private readonly alertService = inject(AlertService);
- private readonly vcsAccessLogEntries = signal([]);
+ protected readonly vcsAccessLogEntries = signal([]);
private readonly params = toSignal(this.route.params, { requireSync: true });
private readonly participationId = computed(() => {
@@ -50,9 +52,9 @@ export class VcsRepositoryAccessLogViewComponent {
await this.extractEntries(() => this.programmingExerciseParticipationService.getVcsAccessLogForRepository(exerciseId, repositoryType));
}
- private async extractEntries(fun: () => Observable) {
+ private async extractEntries(fetchVcsAccessLogs: () => Observable) {
try {
- const accessLogEntries = await lastValueFrom(fun());
+ const accessLogEntries = await lastValueFrom(fetchVcsAccessLogs());
if (accessLogEntries) {
this.vcsAccessLogEntries.set(accessLogEntries);
}
From 5028068ca71816f5a74aaee2798e5ef97a3eb378 Mon Sep 17 00:00:00 2001
From: entholzer
Date: Thu, 12 Sep 2024 14:24:39 +0200
Subject: [PATCH 50/50] resolved conflicts
---
.../domain}/AuthenticationMechanism.java | 2 +-
.../programming/domain}/VcsAccessLog.java | 10 ++++-----
.../dto/VcsAccessLogDTO.java | 4 ++--
.../repository/VcsAccessLogRepository.java | 8 +++----
.../service/RepositoryService.java | 2 +-
.../AutomaticVcsAccessLogCleanupService.java | 4 ++--
.../localvc/LocalVCServletService.java | 3 +--
.../SshGitLocationResolverService.java | 2 +-
.../service/localvc}/VcsAccessLogService.java | 22 +++++++++----------
...grammingExerciseParticipationResource.java | 8 +++----
...VCLocalCIParticipationIntegrationTest.java | 12 +++++-----
11 files changed, 38 insertions(+), 39 deletions(-)
rename src/main/java/de/tum/{in/www1/artemis/domain/vcstokens => cit/aet/artemis/programming/domain}/AuthenticationMechanism.java (91%)
rename src/main/java/de/tum/{in/www1/artemis/domain/vcstokens => cit/aet/artemis/programming/domain}/VcsAccessLog.java (89%)
rename src/main/java/de/tum/cit/aet/artemis/{core => programming}/dto/VcsAccessLogDTO.java (92%)
rename src/main/java/de/tum/cit/aet/artemis/{modeling/service/scheduled => programming/service/localvc}/AutomaticVcsAccessLogCleanupService.java (91%)
rename src/main/java/de/tum/cit/aet/artemis/{modeling/service => programming/service/localvc}/VcsAccessLogService.java (82%)
diff --git a/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/AuthenticationMechanism.java b/src/main/java/de/tum/cit/aet/artemis/programming/domain/AuthenticationMechanism.java
similarity index 91%
rename from src/main/java/de/tum/in/www1/artemis/domain/vcstokens/AuthenticationMechanism.java
rename to src/main/java/de/tum/cit/aet/artemis/programming/domain/AuthenticationMechanism.java
index 108f46e646e5..3caec801ed53 100644
--- a/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/AuthenticationMechanism.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/domain/AuthenticationMechanism.java
@@ -1,4 +1,4 @@
-package de.tum.in.www1.artemis.domain.vcstokens;
+package de.tum.cit.aet.artemis.programming.domain;
public enum AuthenticationMechanism {
/**
diff --git a/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/VcsAccessLog.java b/src/main/java/de/tum/cit/aet/artemis/programming/domain/VcsAccessLog.java
similarity index 89%
rename from src/main/java/de/tum/in/www1/artemis/domain/vcstokens/VcsAccessLog.java
rename to src/main/java/de/tum/cit/aet/artemis/programming/domain/VcsAccessLog.java
index 284c787d1f94..560ca52a31c1 100644
--- a/src/main/java/de/tum/in/www1/artemis/domain/vcstokens/VcsAccessLog.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/domain/VcsAccessLog.java
@@ -1,4 +1,4 @@
-package de.tum.in.www1.artemis.domain.vcstokens;
+package de.tum.cit.aet.artemis.programming.domain;
import java.time.ZonedDateTime;
@@ -14,10 +14,10 @@
import com.fasterxml.jackson.annotation.JsonInclude;
-import de.tum.in.www1.artemis.domain.DomainObject;
-import de.tum.in.www1.artemis.domain.User;
-import de.tum.in.www1.artemis.domain.participation.Participation;
-import de.tum.in.www1.artemis.web.rest.repository.RepositoryActionType;
+import de.tum.cit.aet.artemis.core.domain.DomainObject;
+import de.tum.cit.aet.artemis.core.domain.User;
+import de.tum.cit.aet.artemis.exercise.domain.participation.Participation;
+import de.tum.cit.aet.artemis.programming.web.repository.RepositoryActionType;
/**
* A Vcs access log entry.
diff --git a/src/main/java/de/tum/cit/aet/artemis/core/dto/VcsAccessLogDTO.java b/src/main/java/de/tum/cit/aet/artemis/programming/dto/VcsAccessLogDTO.java
similarity index 92%
rename from src/main/java/de/tum/cit/aet/artemis/core/dto/VcsAccessLogDTO.java
rename to src/main/java/de/tum/cit/aet/artemis/programming/dto/VcsAccessLogDTO.java
index 96abf0bd46ef..b9896cf6efbe 100644
--- a/src/main/java/de/tum/cit/aet/artemis/core/dto/VcsAccessLogDTO.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/dto/VcsAccessLogDTO.java
@@ -1,10 +1,10 @@
-package de.tum.in.www1.artemis.web.rest.dto;
+package de.tum.cit.aet.artemis.programming.dto;
import java.time.ZonedDateTime;
import com.fasterxml.jackson.annotation.JsonInclude;
-import de.tum.in.www1.artemis.domain.vcstokens.VcsAccessLog;
+import de.tum.cit.aet.artemis.programming.domain.VcsAccessLog;
/**
* DTO representing a VCS access log entry.
diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/repository/VcsAccessLogRepository.java b/src/main/java/de/tum/cit/aet/artemis/programming/repository/VcsAccessLogRepository.java
index f71d949f878e..af342179e111 100644
--- a/src/main/java/de/tum/cit/aet/artemis/programming/repository/VcsAccessLogRepository.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/repository/VcsAccessLogRepository.java
@@ -1,6 +1,6 @@
-package de.tum.in.www1.artemis.repository;
+package de.tum.cit.aet.artemis.programming.repository;
-import static de.tum.in.www1.artemis.config.Constants.PROFILE_LOCALVC;
+import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_LOCALVC;
import java.time.ZonedDateTime;
import java.util.List;
@@ -11,8 +11,8 @@
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
-import de.tum.in.www1.artemis.domain.vcstokens.VcsAccessLog;
-import de.tum.in.www1.artemis.repository.base.ArtemisJpaRepository;
+import de.tum.cit.aet.artemis.core.repository.base.ArtemisJpaRepository;
+import de.tum.cit.aet.artemis.programming.domain.VcsAccessLog;
/**
* Spring Data JPA repository for the User entity.
diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/RepositoryService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/RepositoryService.java
index 93672852732f..ed5dd6cbae45 100644
--- a/src/main/java/de/tum/cit/aet/artemis/programming/service/RepositoryService.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/RepositoryService.java
@@ -47,7 +47,7 @@
import de.tum.cit.aet.artemis.programming.domain.RepositoryType;
import de.tum.cit.aet.artemis.programming.domain.VcsRepositoryUri;
import de.tum.cit.aet.artemis.programming.dto.FileMove;
-import de.tum.cit.aet.artemis.service.VcsAccessLogService;
+import de.tum.cit.aet.artemis.programming.service.localvc.VcsAccessLogService;
/**
* Service that provides utilities for managing files in a git repository.
diff --git a/src/main/java/de/tum/cit/aet/artemis/modeling/service/scheduled/AutomaticVcsAccessLogCleanupService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/AutomaticVcsAccessLogCleanupService.java
similarity index 91%
rename from src/main/java/de/tum/cit/aet/artemis/modeling/service/scheduled/AutomaticVcsAccessLogCleanupService.java
rename to src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/AutomaticVcsAccessLogCleanupService.java
index bdad97c2de98..f438a093edf6 100644
--- a/src/main/java/de/tum/cit/aet/artemis/modeling/service/scheduled/AutomaticVcsAccessLogCleanupService.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/AutomaticVcsAccessLogCleanupService.java
@@ -1,4 +1,4 @@
-package de.tum.in.www1.artemis.service.scheduled;
+package de.tum.cit.aet.artemis.programming.service.localvc;
import java.time.ZonedDateTime;
@@ -9,7 +9,7 @@
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
-import de.tum.in.www1.artemis.repository.VcsAccessLogRepository;
+import de.tum.cit.aet.artemis.programming.repository.VcsAccessLogRepository;
@Service
@Profile("scheduling & localvc")
diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/LocalVCServletService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/LocalVCServletService.java
index 8b9f6cdffff9..90186a8c2c31 100644
--- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/LocalVCServletService.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/LocalVCServletService.java
@@ -49,6 +49,7 @@
import de.tum.cit.aet.artemis.core.security.SecurityUtils;
import de.tum.cit.aet.artemis.core.service.AuthorizationCheckService;
import de.tum.cit.aet.artemis.core.util.TimeLogUtil;
+import de.tum.cit.aet.artemis.programming.domain.AuthenticationMechanism;
import de.tum.cit.aet.artemis.programming.domain.Commit;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseParticipation;
@@ -66,8 +67,6 @@
import de.tum.cit.aet.artemis.programming.service.RepositoryAccessService;
import de.tum.cit.aet.artemis.programming.service.ci.ContinuousIntegrationTriggerService;
import de.tum.cit.aet.artemis.programming.web.repository.RepositoryActionType;
-import de.tum.in.www1.artemis.domain.vcstokens.AuthenticationMechanism;
-import de.tum.in.www1.artemis.service.VcsAccessLogService;
/**
* This service is responsible for authenticating and authorizing git requests as well as for retrieving the requested Git repositories from disk.
diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/SshGitLocationResolverService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/SshGitLocationResolverService.java
index 2ab1856ef322..a61712685ef7 100644
--- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/SshGitLocationResolverService.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/SshGitLocationResolverService.java
@@ -21,7 +21,7 @@
import de.tum.cit.aet.artemis.core.exception.EntityNotFoundException;
import de.tum.cit.aet.artemis.core.exception.localvc.LocalVCForbiddenException;
import de.tum.cit.aet.artemis.core.exception.localvc.LocalVCInternalException;
-import de.tum.cit.aet.artemis.domain.vcstokens.AuthenticationMechanism;
+import de.tum.cit.aet.artemis.programming.domain.AuthenticationMechanism;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise;
import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository;
import de.tum.cit.aet.artemis.programming.service.localvc.ssh.SshConstants;
diff --git a/src/main/java/de/tum/cit/aet/artemis/modeling/service/VcsAccessLogService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/VcsAccessLogService.java
similarity index 82%
rename from src/main/java/de/tum/cit/aet/artemis/modeling/service/VcsAccessLogService.java
rename to src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/VcsAccessLogService.java
index 12c59463a6b9..d77b37c02a7f 100644
--- a/src/main/java/de/tum/cit/aet/artemis/modeling/service/VcsAccessLogService.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localvc/VcsAccessLogService.java
@@ -1,6 +1,6 @@
-package de.tum.in.www1.artemis.service;
+package de.tum.cit.aet.artemis.programming.service.localvc;
-import static de.tum.in.www1.artemis.config.Constants.PROFILE_LOCALVC;
+import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_LOCALVC;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
@@ -9,15 +9,15 @@
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
-import de.tum.in.www1.artemis.domain.Repository;
-import de.tum.in.www1.artemis.domain.User;
-import de.tum.in.www1.artemis.domain.participation.Participation;
-import de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseParticipation;
-import de.tum.in.www1.artemis.domain.vcstokens.AuthenticationMechanism;
-import de.tum.in.www1.artemis.domain.vcstokens.VcsAccessLog;
-import de.tum.in.www1.artemis.repository.ParticipationRepository;
-import de.tum.in.www1.artemis.repository.VcsAccessLogRepository;
-import de.tum.in.www1.artemis.web.rest.repository.RepositoryActionType;
+import de.tum.cit.aet.artemis.core.domain.User;
+import de.tum.cit.aet.artemis.exercise.domain.participation.Participation;
+import de.tum.cit.aet.artemis.exercise.repository.ParticipationRepository;
+import de.tum.cit.aet.artemis.programming.domain.AuthenticationMechanism;
+import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseParticipation;
+import de.tum.cit.aet.artemis.programming.domain.Repository;
+import de.tum.cit.aet.artemis.programming.domain.VcsAccessLog;
+import de.tum.cit.aet.artemis.programming.repository.VcsAccessLogRepository;
+import de.tum.cit.aet.artemis.programming.web.repository.RepositoryActionType;
@Profile(PROFILE_LOCALVC)
@Service
diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseParticipationResource.java b/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseParticipationResource.java
index e503bcc0194d..d06fdf5fb975 100644
--- a/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseParticipationResource.java
+++ b/src/main/java/de/tum/cit/aet/artemis/programming/web/ProgrammingExerciseParticipationResource.java
@@ -33,6 +33,7 @@
import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastInstructor;
import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastStudent;
import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastTutor;
+import de.tum.cit.aet.artemis.core.security.annotations.enforceRoleInExercise.EnforceAtLeastInstructorInExercise;
import de.tum.cit.aet.artemis.core.service.AuthorizationCheckService;
import de.tum.cit.aet.artemis.exam.repository.StudentExamRepository;
import de.tum.cit.aet.artemis.exam.service.ExamService;
@@ -44,17 +45,16 @@
import de.tum.cit.aet.artemis.programming.domain.ProgrammingExerciseStudentParticipation;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingSubmission;
import de.tum.cit.aet.artemis.programming.domain.RepositoryType;
+import de.tum.cit.aet.artemis.programming.domain.VcsAccessLog;
import de.tum.cit.aet.artemis.programming.domain.VcsRepositoryUri;
import de.tum.cit.aet.artemis.programming.dto.CommitInfoDTO;
+import de.tum.cit.aet.artemis.programming.dto.VcsAccessLogDTO;
import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseRepository;
import de.tum.cit.aet.artemis.programming.repository.ProgrammingExerciseStudentParticipationRepository;
+import de.tum.cit.aet.artemis.programming.repository.VcsAccessLogRepository;
import de.tum.cit.aet.artemis.programming.service.ProgrammingExerciseParticipationService;
import de.tum.cit.aet.artemis.programming.service.ProgrammingSubmissionService;
import de.tum.cit.aet.artemis.programming.service.RepositoryService;
-import de.tum.in.www1.artemis.domain.vcstokens.VcsAccessLog;
-import de.tum.in.www1.artemis.repository.VcsAccessLogRepository;
-import de.tum.in.www1.artemis.security.annotations.enforceRoleInExercise.EnforceAtLeastInstructorInExercise;
-import de.tum.in.www1.artemis.web.rest.dto.VcsAccessLogDTO;
@Profile(PROFILE_CORE)
@RestController
diff --git a/src/test/java/de/tum/cit/aet/artemis/localvcci/LocalVCLocalCIParticipationIntegrationTest.java b/src/test/java/de/tum/cit/aet/artemis/localvcci/LocalVCLocalCIParticipationIntegrationTest.java
index 37a0e61b03a4..1ac8510ab262 100644
--- a/src/test/java/de/tum/cit/aet/artemis/localvcci/LocalVCLocalCIParticipationIntegrationTest.java
+++ b/src/test/java/de/tum/cit/aet/artemis/localvcci/LocalVCLocalCIParticipationIntegrationTest.java
@@ -15,16 +15,16 @@
import de.tum.cit.aet.artemis.core.domain.User;
import de.tum.cit.aet.artemis.exercise.domain.participation.StudentParticipation;
import de.tum.cit.aet.artemis.exercise.programming.ProgrammingExerciseUtilService;
+import de.tum.cit.aet.artemis.participation.ParticipationUtilService;
+import de.tum.cit.aet.artemis.programming.domain.AuthenticationMechanism;
import de.tum.cit.aet.artemis.programming.domain.ProgrammingExercise;
import de.tum.cit.aet.artemis.programming.domain.TemplateProgrammingExerciseParticipation;
+import de.tum.cit.aet.artemis.programming.domain.VcsAccessLog;
+import de.tum.cit.aet.artemis.programming.dto.VcsAccessLogDTO;
+import de.tum.cit.aet.artemis.programming.repository.VcsAccessLogRepository;
import de.tum.cit.aet.artemis.programming.service.localvc.LocalVCRepositoryUri;
+import de.tum.cit.aet.artemis.programming.web.repository.RepositoryActionType;
import de.tum.cit.aet.artemis.util.LocalRepository;
-import de.tum.in.www1.artemis.domain.vcstokens.AuthenticationMechanism;
-import de.tum.in.www1.artemis.domain.vcstokens.VcsAccessLog;
-import de.tum.in.www1.artemis.participation.ParticipationUtilService;
-import de.tum.in.www1.artemis.repository.VcsAccessLogRepository;
-import de.tum.in.www1.artemis.web.rest.dto.VcsAccessLogDTO;
-import de.tum.in.www1.artemis.web.rest.repository.RepositoryActionType;
class LocalVCLocalCIParticipationIntegrationTest extends AbstractSpringIntegrationLocalCILocalVCTest {