diff --git a/.idea/runConfigurations/Artemis__Server__LocalVC___LocalCI_.xml b/.idea/runConfigurations/Artemis__Server__LocalVC___LocalCI_.xml
index d90fd42fe681..bf04b8954c1b 100644
--- a/.idea/runConfigurations/Artemis__Server__LocalVC___LocalCI_.xml
+++ b/.idea/runConfigurations/Artemis__Server__LocalVC___LocalCI_.xml
@@ -10,4 +10,4 @@
-
+
\ No newline at end of file
diff --git a/src/main/java/de/tum/in/www1/artemis/config/Constants.java b/src/main/java/de/tum/in/www1/artemis/config/Constants.java
index f85699d9ae4f..bd433b712884 100644
--- a/src/main/java/de/tum/in/www1/artemis/config/Constants.java
+++ b/src/main/java/de/tum/in/www1/artemis/config/Constants.java
@@ -285,6 +285,11 @@ public final class Constants {
*/
public static final String PROFILE_LOCALCI = "localci";
+ /**
+ * The name of the Spring profile used for gitlab.
+ */
+ public static final String PROFILE_GITLAB = "gitlab";
+
/**
* The name of the Spring profile used to process build jobs in a local CI setup.
*/
diff --git a/src/main/java/de/tum/in/www1/artemis/repository/UserRepository.java b/src/main/java/de/tum/in/www1/artemis/repository/UserRepository.java
index efcd1f993bfa..e51af5e93f16 100644
--- a/src/main/java/de/tum/in/www1/artemis/repository/UserRepository.java
+++ b/src/main/java/de/tum/in/www1/artemis/repository/UserRepository.java
@@ -569,6 +569,17 @@ OR CONCAT(user.firstName, ' ', user.lastName) LIKE %:#{#loginOrName}%
""")
void updateUserSshPublicKeyHash(@Param("userId") long userId, @Param("sshPublicKeyHash") String sshPublicKeyHash, @Param("sshPublicKey") String sshPublicKey);
+ @Modifying
+ @Transactional // ok because of modifying query
+ @Query("""
+ UPDATE User user
+ SET user.vcsAccessToken = :vcsAccessToken,
+ user.vcsAccessTokenExpiryDate = :vcsAccessTokenExpiryDate
+ WHERE user.id = :userId
+ """)
+ void updateUserVCAccessToken(@Param("userId") long userId, @Param("vcsAccessToken") String vcsAccessToken,
+ @Param("vcsAccessTokenExpiryDate") ZonedDateTime vcsAccessTokenExpiryDate);
+
@Modifying
@Transactional // ok because of modifying query
@Query("""
diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/gitlab/GitlabInfoContributor.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/gitlab/GitlabInfoContributor.java
index 6465d6186cbe..38f28476ca6e 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/connectors/gitlab/GitlabInfoContributor.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/gitlab/GitlabInfoContributor.java
@@ -1,5 +1,7 @@
package de.tum.in.www1.artemis.service.connectors.gitlab;
+import static de.tum.in.www1.artemis.config.Constants.PROFILE_GITLAB;
+
import java.net.URL;
import java.util.Optional;
@@ -12,7 +14,7 @@
import de.tum.in.www1.artemis.config.Constants;
@Component
-@Profile("gitlab")
+@Profile(PROFILE_GITLAB)
public class GitlabInfoContributor implements InfoContributor {
@Value("${artemis.version-control.url}")
diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCInfoContributor.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCInfoContributor.java
index 358b0c9490fd..3f5223b0526f 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCInfoContributor.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCInfoContributor.java
@@ -43,7 +43,7 @@ public void contribute(Info.Builder builder) {
// TODO: only activate this when access tokens are available and make sure this does not lead to issues
// TODO: If activated, reflect this in LocalVCInfoContributorTest
// with the account.service.ts and its check if the access token is required
- builder.withDetail(Constants.INFO_VERSION_CONTROL_ACCESS_TOKEN_DETAIL, false);
+ builder.withDetail(Constants.INFO_VERSION_CONTROL_ACCESS_TOKEN_DETAIL, true);
// Store ssh url template
try {
diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCPersonalAccessTokenManagementService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCPersonalAccessTokenManagementService.java
new file mode 100644
index 000000000000..372f3337155e
--- /dev/null
+++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/localvc/LocalVCPersonalAccessTokenManagementService.java
@@ -0,0 +1,74 @@
+package de.tum.in.www1.artemis.service.connectors.localvc;
+
+import static de.tum.in.www1.artemis.config.Constants.PROFILE_LOCALVC;
+
+import java.security.SecureRandom;
+import java.time.Duration;
+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.stereotype.Service;
+
+import de.tum.in.www1.artemis.domain.User;
+import de.tum.in.www1.artemis.repository.UserRepository;
+import de.tum.in.www1.artemis.service.connectors.vcs.VcsTokenManagementService;
+import de.tum.in.www1.artemis.web.rest.UserResource;
+
+@Service
+@Profile(PROFILE_LOCALVC)
+public class LocalVCPersonalAccessTokenManagementService extends VcsTokenManagementService {
+
+ private static final Logger log = LoggerFactory.getLogger(UserResource.class);
+
+ private final UserRepository userRepository;
+
+ @Value("${artemis.version-control.version-control-access-token:#{false}}")
+ private Boolean versionControlAccessToken;
+
+ @Value("${artemis.version-control.vc-access-token-max-lifetime-in-days:365}")
+ private int vcMaxLifetimeInDays;
+
+ private static final String TOKEN_PREFIX = "vcpat-";
+
+ private static final int RANDOM_STRING_LENGTH = 40;
+
+ private static final String CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+
+ public LocalVCPersonalAccessTokenManagementService(UserRepository userRepository) {
+ super();
+ this.userRepository = userRepository;
+ }
+
+ @Override
+ public void createAccessToken(User user, Duration lifetime) {
+ if (versionControlAccessToken) {
+ userRepository.updateUserVCAccessToken(user.getId(), generateSecureToken(), ZonedDateTime.now().plus(Duration.ofDays(vcMaxLifetimeInDays)));
+ log.info("Created access token for user {}", user.getId());
+ }
+ }
+
+ @Override
+ public void renewAccessToken(User user, Duration newLifetime) {
+ if (versionControlAccessToken) {
+ if (user.getVcsAccessTokenExpiryDate() != null && user.getVcsAccessTokenExpiryDate().isBefore(ZonedDateTime.now())) {
+ // todo create new one here and
+ userRepository.updateUserVCAccessToken(user.getId(), user.getVcsAccessToken(), ZonedDateTime.now().plus(Duration.ofDays(vcMaxLifetimeInDays)));
+ }
+ log.info("Renewed access token for user {}", user.getId());
+ }
+ }
+
+ public static String generateSecureToken() {
+ SecureRandom secureRandom = new SecureRandom();
+ StringBuilder randomString = new StringBuilder(RANDOM_STRING_LENGTH);
+
+ for (int i = 0; i < RANDOM_STRING_LENGTH; i++) {
+ int randomIndex = secureRandom.nextInt(CHARACTERS.length());
+ randomString.append(CHARACTERS.charAt(randomIndex));
+ }
+ return TOKEN_PREFIX + randomString.toString();
+ }
+}
diff --git a/src/main/java/de/tum/in/www1/artemis/service/connectors/vcs/VcsTokenRenewalService.java b/src/main/java/de/tum/in/www1/artemis/service/connectors/vcs/VcsTokenRenewalService.java
index 784a1a31bb77..6fd5ef217464 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/connectors/vcs/VcsTokenRenewalService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/connectors/vcs/VcsTokenRenewalService.java
@@ -50,6 +50,7 @@ public VcsTokenRenewalService(@Value("${artemis.version-control.version-control-
this.versionControlAccessToken = versionControlAccessToken;
this.vcsTokenManagementService = vcsTokenManagementService;
this.userRepository = userRepository;
+ renewAllVcsAccessTokens();
}
/**
diff --git a/src/main/java/de/tum/in/www1/artemis/service/user/UserCreationService.java b/src/main/java/de/tum/in/www1/artemis/service/user/UserCreationService.java
index 7d97eb892bf7..105915402798 100644
--- a/src/main/java/de/tum/in/www1/artemis/service/user/UserCreationService.java
+++ b/src/main/java/de/tum/in/www1/artemis/service/user/UserCreationService.java
@@ -1,12 +1,15 @@
package de.tum.in.www1.artemis.service.user;
import static de.tum.in.www1.artemis.config.Constants.PROFILE_CORE;
+import static de.tum.in.www1.artemis.config.Constants.PROFILE_LOCALVC;
import static de.tum.in.www1.artemis.security.Role.EDITOR;
import static de.tum.in.www1.artemis.security.Role.INSTRUCTOR;
import static de.tum.in.www1.artemis.security.Role.STUDENT;
import static de.tum.in.www1.artemis.security.Role.TEACHING_ASSISTANT;
+import java.time.Duration;
import java.time.Instant;
+import java.time.ZonedDateTime;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;
@@ -32,7 +35,9 @@
import de.tum.in.www1.artemis.repository.OrganizationRepository;
import de.tum.in.www1.artemis.repository.UserRepository;
import de.tum.in.www1.artemis.security.SecurityUtils;
+import de.tum.in.www1.artemis.service.ProfileService;
import de.tum.in.www1.artemis.service.connectors.ci.CIUserManagementService;
+import de.tum.in.www1.artemis.service.connectors.localvc.LocalVCPersonalAccessTokenManagementService;
import de.tum.in.www1.artemis.service.connectors.vcs.VcsUserManagementService;
import de.tum.in.www1.artemis.web.rest.vm.ManagedUserVM;
import tech.jhipster.security.RandomUtil;
@@ -41,6 +46,8 @@
@Service
public class UserCreationService {
+ private final ProfileService profileService;
+
@Value("${artemis.user-management.use-external}")
private Boolean useExternalUserManagement;
@@ -56,6 +63,12 @@ public class UserCreationService {
@Value("${info.guided-tour.course-group-instructors:#{null}}")
private Optional tutorialGroupInstructors;
+ @Value("${artemis.version-control.version-control-access-token:#{false}}")
+ private Boolean versionControlAccessToken;
+
+ @Value("${artemis.version-control.vc-access-token-max-lifetime-in-days:365}")
+ private int vcMaxLifetimeInDays;
+
private static final Logger log = LoggerFactory.getLogger(UserCreationService.class);
private final UserRepository userRepository;
@@ -76,7 +89,7 @@ public class UserCreationService {
public UserCreationService(UserRepository userRepository, PasswordService passwordService, AuthorityRepository authorityRepository, CourseRepository courseRepository,
Optional optionalVcsUserManagementService, Optional optionalCIUserManagementService, CacheManager cacheManager,
- OrganizationRepository organizationRepository) {
+ OrganizationRepository organizationRepository, ProfileService profileService) {
this.userRepository = userRepository;
this.passwordService = passwordService;
this.authorityRepository = authorityRepository;
@@ -85,6 +98,7 @@ public UserCreationService(UserRepository userRepository, PasswordService passwo
this.optionalCIUserManagementService = optionalCIUserManagementService;
this.cacheManager = cacheManager;
this.organizationRepository = organizationRepository;
+ this.profileService = profileService;
}
/**
@@ -130,6 +144,11 @@ public User createUser(String login, @Nullable String password, @Nullable Set new HashSet<>(Set.of(...))
@@ -185,6 +204,12 @@ public User createUser(ManagedUserVM userDTO) {
catch (InvalidDataAccessApiUsageException | PatternSyntaxException pse) {
log.warn("Could not retrieve matching organizations from pattern: {}", pse.getMessage());
}
+
+ // new users gets new VCS access tokens, for the LocalVC setup
+ if (versionControlAccessToken && profileService.isProfileActive(PROFILE_LOCALVC)) {
+ user.setVcsAccessToken(LocalVCPersonalAccessTokenManagementService.generateSecureToken());
+ user.setVcsAccessTokenExpiryDate(ZonedDateTime.now().plus(Duration.ofDays(vcMaxLifetimeInDays)));
+ }
user.setGroups(userDTO.getGroups());
user.setActivated(true);
user.setInternal(true);
diff --git a/src/main/resources/config/application-localvc.yml b/src/main/resources/config/application-localvc.yml
index 482df6390d9e..d246c474135d 100644
--- a/src/main/resources/config/application-localvc.yml
+++ b/src/main/resources/config/application-localvc.yml
@@ -12,5 +12,5 @@ artemis:
url: http://localhost:8000
build-agent-git-username: buildjob_user # Replace with more secure credentials for production. Required for https access to localvc. This config must be set for build agents and localvc.
build-agent-git-password: buildjob_password # Replace with more secure credentials for production. Required for https access to localvc. This config must be set for build agents and localvc. You can also use an ssh key
-
+ vc-access-token-max-lifetime-in-days: 365
diff --git a/src/test/java/de/tum/in/www1/artemis/localvcci/LocalVCInfoContributorTest.java b/src/test/java/de/tum/in/www1/artemis/localvcci/LocalVCInfoContributorTest.java
index e08769b1e46d..0210bbd5f5ad 100644
--- a/src/test/java/de/tum/in/www1/artemis/localvcci/LocalVCInfoContributorTest.java
+++ b/src/test/java/de/tum/in/www1/artemis/localvcci/LocalVCInfoContributorTest.java
@@ -25,7 +25,7 @@ void testContribute() {
}
Info info = builder.build();
- assertThat((Boolean) info.getDetails().get("versionControlAccessToken")).isFalse();
+ assertThat((Boolean) info.getDetails().get("versionControlAccessToken")).isTrue();
}
}