Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
f2280f2
Add username and password fields to StartAnalysis component
JanaNF Nov 22, 2025
77bd888
Remove log following and shutdown handling from docker_start.sh
JanaNF Nov 22, 2025
33b7407
Remove application.yml configuration file
JanaNF Nov 22, 2025
e852fb0
Implement credential-based login functionality with JWT authentication
JanaNF Nov 22, 2025
3236872
Implement dynamic authentication and repository fetching with encrypt…
JanaNF Nov 23, 2025
df7eef0
Refactor data transformation comments and add start analysis flow dia…
JanaNF Nov 23, 2025
a68c450
Refactor authentication flow in start_analysis_flow diagram to includ…
JanaNF Nov 23, 2025
1078750
Remove application.yml from .gitignore and add configuration file for…
JanaNF Nov 24, 2025
863873d
chore: update OpenAPI spec and generated client
github-actions[bot] Nov 24, 2025
3895c81
Refactor authentication and encryption logic; update API calls to rem…
JanaNF Nov 24, 2025
82e023e
Merge branch 'feature/credential-login' of https://github.com/ls1intu…
JanaNF Nov 24, 2025
dd6f1be
feat: add validation to LoginRequestDTO and simplify AuthController l…
JanaNF Nov 24, 2025
1c93849
refactor: clean up code formatting and remove unused imports in vario…
JanaNF Nov 24, 2025
750c651
chore: update OpenAPI spec and generated client
github-actions[bot] Nov 24, 2025
d268969
Remove accidentally pushed team repos and add Projects/ to gitignore
JanaNF Nov 29, 2025
d87624d
fix: remove optional parameters from onStart function in StartAnalysi…
JanaNF Nov 29, 2025
18eb5b6
feat: implement AuthResource for user authentication and cookie manag…
JanaNF Nov 29, 2025
8781966
feat: introduce GitOperationException for improved error handling in …
JanaNF Nov 29, 2025
56cce72
fix: remove redundant exception rethrow in authenticate method
JanaNF Nov 29, 2025
32aea4c
feat: refactor authentication handling to use ArtemisCredentials DTO …
JanaNF Nov 29, 2025
8563039
feat: add TestCredentialsLoader for dynamic authentication and manage…
JanaNF Nov 29, 2025
583439c
Merge main and resolve conflicts in dataLoaders.ts
JanaNF Nov 29, 2025
1943d1b
fix: update TODO comments to use 'server' instead of 'backend' for co…
JanaNF Nov 29, 2025
f69b5ac
chore: update OpenAPI spec and generated client
github-actions[bot] Nov 29, 2025
24ceefd
fix: remove trailing whitespace in ArtemisCredentials methods for cle…
JanaNF Nov 29, 2025
b27abe8
Merge branch 'feature/credential-login' of https://github.com/ls1intu…
JanaNF Nov 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ out/

### VS Code ###
.vscode/

Comment thread
JanaNF marked this conversation as resolved.
Outdated
Submodule ge64zohtestcourseitpseminar-team1 added at 755619
Submodule ge64zohtestcourseitpseminar-team2 added at a03c2d
55 changes: 55 additions & 0 deletions docs/architecture/start_analysis_flow.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
@startuml StartAnalysisFlow
title Start Analysis Flow

actor User
participant "Client (React)" as Client
participant "AuthController" as Auth
participant "RequestResource" as Controller
participant "RequestService" as ReqService
participant "RepositoryFetchingService" as RepoService
participant "ArtemisClientService" as ArtemisClient
participant "GitOperationsService" as GitService
participant "Artemis Server" as Artemis
participant "Git Server" as Git

== Authentication Phase ==
User -> Client: Enter Credentials & Click "Start"
Client -> Auth: POST /api/auth/login
note right of Client: Sends username, password, serverUrl
Auth -> ArtemisClient: authenticate(url, user, pass)
ArtemisClient -> Artemis: POST /api/core/public/authenticate
Artemis --> ArtemisClient: 200 OK (Set-Cookie: jwt)
ArtemisClient --> Auth: jwtToken
Auth -> Client: 200 OK (Set-Cookie: jwt, username, password)
Client -> Client: Navigate to /teams

== Data Loading Phase ==
Client -> Controller: GET /api/requestResource/fetchAndCloneRepositories
note right of Client: Cookies included automatically
Controller -> Controller: Extract & Decrypt Credentials
Controller -> ReqService: fetchAndCloneRepositories(url, jwt, user, pass)
ReqService -> RepoService: fetchAndCloneRepositories(url, jwt, user, pass)

== Fetching Participations ==
RepoService -> ArtemisClient: fetchParticipations(url, jwt)
ArtemisClient -> Artemis: GET /api/exercise/exercises/{id}/participations
Artemis --> ArtemisClient: List<ParticipationDTO>
ArtemisClient --> RepoService: List<ParticipationDTO>

== Cloning Repositories ==
loop For each Participation
RepoService -> GitService: cloneOrPullRepository(uri, teamName, user, pass)
GitService -> Git: git clone / git pull
note right of GitService: Uses Instructor Username/Password directly
Git --> GitService: Repository Content
GitService --> RepoService: Local Path
end

== Response Phase ==
RepoService --> ReqService: List<TeamRepositoryDTO>
ReqService --> Controller: List<TeamRepositoryDTO>
Controller --> Client: 200 OK (JSON List)
Client -> Client: Transform Data (Mock Analysis)
Client -> User: Display Team Cards

@enduml
35 changes: 35 additions & 0 deletions openapi/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,38 @@ info: {title: OpenAPI definition, version: v0}
servers:
- {url: 'http://localhost:8080', description: Generated server url}
paths:
/api/auth/login:
post:
tags: [auth-controller]
operationId: login
requestBody:
content:
application/json:
schema: {$ref: '#/components/schemas/LoginRequestDTO'}
required: true
responses:
'200': {description: OK}
/api/requestResource/fetchAndCloneRepositories:
get:
tags: [request-resource]
operationId: fetchAndCloneRepositories
parameters:
- name: jwt
in: cookie
required: false
schema: {type: string}
- name: artemis_server_url
in: cookie
required: false
schema: {type: string}
- name: artemis_username
in: cookie
required: false
schema: {type: string}
- name: artemis_password
in: cookie
required: false
schema: {type: string}
responses:
'200':
description: OK
Expand All @@ -17,6 +45,13 @@ paths:
items: {$ref: '#/components/schemas/TeamRepositoryDTO'}
components:
schemas:
LoginRequestDTO:
type: object
properties:
password: {type: string, minLength: 1}
serverUrl: {type: string, minLength: 1}
username: {type: string, minLength: 1}
required: [password, serverUrl, username]
ParticipantDTO:
type: object
properties:
Expand Down
6 changes: 0 additions & 6 deletions scripts/docker_start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,4 @@ echo -e "\n${YELLOW}Useful commands:${NC}"
echo -e "${YELLOW}• View logs:${NC} docker compose -f docker/docker-compose.yml logs -f"
echo -e "${YELLOW}• Stop services:${NC} docker compose -f docker/docker-compose.yml down"
echo -e "${YELLOW}• Restart services:${NC} docker compose -f docker/docker-compose.yml restart"
echo -e "\n${YELLOW}Press Ctrl+C to stop all services${NC}\n"

# Keep script running and handle Ctrl+C
trap "echo -e '\n${RED}Shutting down all services...${NC}'; docker compose -f docker/docker-compose.yml down; exit" INT

# Follow logs to keep script running
docker compose -f docker/docker-compose.yml logs -f
4 changes: 0 additions & 4 deletions src/main/java/de/tum/cit/aet/core/config/ArtemisConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,6 @@
@Setter
public class ArtemisConfig {

private String baseUrl;
private String username;
private String password;
private String jwtToken;
private Long exerciseId;
private String gitRepoPath;
private Integer numThreads;
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/de/tum/cit/aet/core/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http,
.cors(cors -> cors.configurationSource(corsConfigurationSource))
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api-docs", "/api-docs.yaml", "/actuator/health")
.requestMatchers("/api-docs", "/api-docs.yaml", "/actuator/health", "/api/auth/**")
.permitAll()
.anyRequest().authenticated()
)
.httpBasic(_ -> {});
.httpBasic(basic -> {});
return http.build();
}

Expand Down
82 changes: 82 additions & 0 deletions src/main/java/de/tum/cit/aet/core/security/CryptoService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package de.tum.cit.aet.core.security;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;

@Service
public class CryptoService {

private static final String ALGORITHM = "AES/GCM/NoPadding";
private static final int GCM_TAG_LENGTH = 128;
private static final int GCM_IV_LENGTH = 12;

private final SecretKeySpec secretKey;

public CryptoService(@Value("${harmonia.security.secret-key:default-secret-key-change-me-in-prod}") String secret) {
this.secretKey = generateKey(secret);
}

private SecretKeySpec generateKey(String myKey) {
try {
byte[] key = myKey.getBytes(StandardCharsets.UTF_8);
MessageDigest sha = MessageDigest.getInstance("SHA-256");
key = sha.digest(key);
key = Arrays.copyOf(key, 16); // Use only first 128 bit
return new SecretKeySpec(key, "AES");
} catch (Exception e) {
throw new RuntimeException("Error generating security key", e);
}
}

/**
* Encrypts a string using AES encryption.
*
* @param strToEncrypt The string to encrypt
* @return The encrypted string (Base64 encoded)
*/
public String encrypt(String strToEncrypt) {
try {
byte[] iv = new byte[GCM_IV_LENGTH];
new SecureRandom().nextBytes(iv);
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, spec);
byte[] cipherText = cipher.doFinal(strToEncrypt.getBytes(StandardCharsets.UTF_8));
byte[] encrypted = new byte[iv.length + cipherText.length];
System.arraycopy(iv, 0, encrypted, 0, iv.length);
System.arraycopy(cipherText, 0, encrypted, iv.length, cipherText.length);
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
throw new RuntimeException("Error while encrypting", e);
}
}

/**
* Decrypts a string using AES encryption.
*
* @param strToDecrypt The string to decrypt (Base64 encoded)
* @return The decrypted string
*/
public String decrypt(String strToDecrypt) {
try {
byte[] decoded = Base64.getDecoder().decode(strToDecrypt);
byte[] iv = new byte[GCM_IV_LENGTH];
System.arraycopy(decoded, 0, iv, 0, iv.length);
GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LENGTH, iv);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKey, spec);
return new String(cipher.doFinal(decoded, GCM_IV_LENGTH, decoded.length - GCM_IV_LENGTH));
} catch (Exception e) {
throw new RuntimeException("Error while decrypting", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@ public RequestService(RepositoryFetchingService repositoryFetchingService) {
}

/**
* Fetches and clones all repositories from Artemis.
* Fetches and clones all repositories from Artemis using dynamic credentials.
*
* @param serverUrl The Artemis server URL
* @param jwtToken The JWT token
* @param username The username (optional, for fallback)
* @param password The password (optional, for fallback)
* @return List of TeamRepositoryDTO containing repository information
*/
public List<TeamRepositoryDTO> fetchAndCloneRepositories() {
log.info("RequestService: Initiating repository fetch and clone process");
return repositoryFetchingService.fetchAndCloneRepositories();
public List<TeamRepositoryDTO> fetchAndCloneRepositories(String serverUrl, String jwtToken, String username, String password) {
Comment thread
JanaNF marked this conversation as resolved.
Outdated
log.info("RequestService: Initiating repository fetch and clone process (Dynamic Auth)");
return repositoryFetchingService.fetchAndCloneRepositories(serverUrl, jwtToken, username, password);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package de.tum.cit.aet.dataProcessing.web;

import de.tum.cit.aet.core.security.CryptoService;
import de.tum.cit.aet.dataProcessing.service.RequestService;
import de.tum.cit.aet.repositoryProcessing.dto.TeamRepositoryDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -17,22 +19,52 @@
public class RequestResource {

private final RequestService requestService;
private final CryptoService cryptoService;

@Autowired
public RequestResource(RequestService requestService) {
public RequestResource(RequestService requestService, CryptoService cryptoService) {
this.requestService = requestService;
this.cryptoService = cryptoService;
}

/**
* GET endpoint to fetch and clone all repositories.
* Triggers the fetching of participations from Artemis and clones/pulls all repositories.
*
* @param jwtToken The JWT token from the cookie
* @param serverUrl The Artemis server URL from the cookie
* @param username The Artemis username from the cookie (optional)
* @param encryptedPassword The encrypted Artemis password from the cookie (optional)
* @return ResponseEntity containing the list of TeamRepositoryDTO
*/
@GetMapping("fetchAndCloneRepositories")
public ResponseEntity<List<TeamRepositoryDTO>> fetchAndCloneRepositories() {
public ResponseEntity<List<TeamRepositoryDTO>> fetchAndCloneRepositories(
@CookieValue(value = "jwt", required = false) String jwtToken,
@CookieValue(value = "artemis_server_url", required = false) String serverUrl,
@CookieValue(value = "artemis_username", required = false) String username,
@CookieValue(value = "artemis_password", required = false) String encryptedPassword
Comment thread
JanaNF marked this conversation as resolved.
) {
log.info("GET request received: fetchAndCloneRepositories");
List<TeamRepositoryDTO> repositories = requestService.fetchAndCloneRepositories();

List<TeamRepositoryDTO> repositories;
if (jwtToken != null && serverUrl != null) {
log.info("Using dynamic credentials from cookies");

String password = null;
if (encryptedPassword != null) {
try {
password = cryptoService.decrypt(encryptedPassword);
} catch (Exception e) {
log.error("Failed to decrypt password from cookie", e);
}
}

repositories = requestService.fetchAndCloneRepositories(serverUrl, jwtToken, username, password);
} else {
log.warn("No credentials found in cookies. Authentication required.");
return ResponseEntity.status(401).build();
}

log.info("Successfully fetched and cloned {} repositories", repositories.size());
return ResponseEntity.ok(repositories);
}
Expand Down
Loading
Loading