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);