Skip to content
Open

Fixes #100

Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,31 @@ public class CodeRepoController {
private final CodeRepoApiService codeRepoApiService;
private final DeleteCodeRepoService deleteCodeRepoService;

private ResponseEntity<StatusDTO> buildCreateRepoErrorResponse(Exception e) {
Throwable rootCause = e;
while (rootCause.getCause() != null) {
rootCause = rootCause.getCause();
}
String message = rootCause.getMessage();
boolean repoAlreadyExists = message != null && message.toLowerCase().contains("already exists");
if (repoAlreadyExists) {
return new ResponseEntity<>(new StatusDTO("Repository already exists."), HttpStatus.CONFLICT);
}
return new ResponseEntity<>(
new StatusDTO("Error during repository import. Please contact administrator."),
HttpStatus.BAD_REQUEST
);
}

@PreAuthorize("hasAuthority('USER')")
@PostMapping(value= "/api/v1/coderepo/create/gitlab")
public ResponseEntity<StatusDTO> createCodeRepoGitlab(@Valid @RequestBody CreateCodeRepoRequestDto createCodeRepoRequestDto, Principal principal){
try {
createCodeRepoService.createCodeRepo(createCodeRepoRequestDto, CodeRepo.RepoType.GITLAB).block();
return new ResponseEntity<>(new StatusDTO("ok"), HttpStatus.CREATED);
} catch (Exception e){
e.printStackTrace();
log.error("[CodeRepo] Error Creating CodeRepo {} by {}", createCodeRepoRequestDto.getName(), principal.getName());
return new ResponseEntity<>(new StatusDTO("Not ok"), HttpStatus.BAD_REQUEST);
log.error("[CodeRepo] Error creating CodeRepo {} by {}: {}", createCodeRepoRequestDto.getName(), principal.getName(), e.getMessage());
return buildCreateRepoErrorResponse(e);
}
}
@PreAuthorize("hasAuthority('USER')")
Expand All @@ -56,9 +71,8 @@ public ResponseEntity<StatusDTO> createCodeRepoGitHub(@Valid @RequestBody Create
createCodeRepoService.createCodeRepo(createCodeRepoRequestDto, CodeRepo.RepoType.GITHUB).block();
return new ResponseEntity<>(new StatusDTO("ok"), HttpStatus.CREATED);
} catch (Exception e){
e.printStackTrace();
log.error("[CodeRepo] Error Creating CodeRepo {} by {}", createCodeRepoRequestDto.getName(), principal.getName());
return new ResponseEntity<>(new StatusDTO("Not ok"), HttpStatus.BAD_REQUEST);
log.error("[CodeRepo] Error creating CodeRepo {} by {}: {}", createCodeRepoRequestDto.getName(), principal.getName(), e.getMessage());
return buildCreateRepoErrorResponse(e);
}
}

Expand All @@ -69,9 +83,8 @@ public ResponseEntity<StatusDTO> createCodeRepoGitea(@Valid @RequestBody CreateC
createCodeRepoService.createCodeRepo(createCodeRepoRequestDto, CodeRepo.RepoType.GITEA).block();
return new ResponseEntity<>(new StatusDTO("ok"), HttpStatus.CREATED);
} catch (Exception e){
e.printStackTrace();
log.error("[CodeRepo] Error Creating CodeRepo {} by {}", createCodeRepoRequestDto.getName(), principal.getName());
return new ResponseEntity<>(new StatusDTO("Not ok"), HttpStatus.BAD_REQUEST);
log.error("[CodeRepo] Error creating CodeRepo {} by {}: {}", createCodeRepoRequestDto.getName(), principal.getName(), e.getMessage());
return buildCreateRepoErrorResponse(e);
}
}

Expand All @@ -82,9 +95,8 @@ public ResponseEntity<StatusDTO> createCodeRepoBitbucket(@Valid @RequestBody Cre
createCodeRepoService.createCodeRepo(createCodeRepoRequestDto, CodeRepo.RepoType.BITBUCKET).block();
return new ResponseEntity<>(new StatusDTO("ok"), HttpStatus.CREATED);
} catch (Exception e){
e.printStackTrace();
log.error("[CodeRepo] Error Creating CodeRepo {} by {}", createCodeRepoRequestDto.getName(), principal.getName());
return new ResponseEntity<>(new StatusDTO("Not ok"), HttpStatus.BAD_REQUEST);
log.error("[CodeRepo] Error creating CodeRepo {} by {}: {}", createCodeRepoRequestDto.getName(), principal.getName(), e.getMessage());
return buildCreateRepoErrorResponse(e);
}
}

Expand Down Expand Up @@ -228,15 +240,30 @@ public ResponseEntity<StatusDTO> bulkChangeTeam(@Valid @RequestBody BulkChangeTe
}
}

@PreAuthorize("hasAuthority('ADMIN')")
@PutMapping(value = "/api/v1/coderepo/bulk/change-token")
@PreAuthorize("hasAnyAuthority('ADMIN','TEAM_MANAGER')")
@PostMapping(value = "/api/v1/coderepo/bulk/change-token")
public ResponseEntity<StatusDTO> bulkChangeToken(@Valid @RequestBody BulkChangeTokenRequestDto request, Principal principal) {
try {
codeRepoApiService.bulkChangeAccessToken(request.getRepositoryIds(), request.getAccessToken());
codeRepoApiService.bulkChangeAccessToken(request.getRepositoryIds(), request.getAccessToken(), principal);
return ResponseEntity.ok(new StatusDTO("Access token updated for selected repositories."));
} catch (Exception e) {
log.error("[CodeRepo] Error during bulk token change by {}: {}", principal.getName(), e.getMessage());
return new ResponseEntity<>(new StatusDTO(e.getMessage()), HttpStatus.BAD_REQUEST);
return new ResponseEntity<>(new StatusDTO("Error during access token update."), HttpStatus.BAD_REQUEST);
}
}

@PreAuthorize("hasAnyAuthority('ADMIN','TEAM_MANAGER')")
@PostMapping(value = "/api/v1/coderepo/{id}/change-token")
public ResponseEntity<StatusDTO> changeToken(
@PathVariable("id") Long id,
@Valid @RequestBody ChangeAccessTokenRequestDto request,
Principal principal) {
try {
codeRepoApiService.changeAccessToken(id, request.getAccessToken(), principal);
return ResponseEntity.ok(new StatusDTO("Access token updated for repository."));
} catch (Exception e) {
log.error("[CodeRepo] Error during token change for repo {} by {}: {}", id, principal.getName(), e.getMessage());
return new ResponseEntity<>(new StatusDTO("Error during access token update."), HttpStatus.BAD_REQUEST);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.mixeway.mixewayflowapi.api.coderepo.dto;

import jakarta.validation.constraints.NotBlank;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class ChangeAccessTokenRequestDto {

@NotBlank(message = "Access token cannot be empty.")
private String accessToken;
}
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,19 @@ public void bulkChangeTeam(List<Long> repositoryIds, Long newTeamId, Principal p
updateCodeRepoService.bulkChangeTeam(repositoryIds, newTeam);
}

public void bulkChangeAccessToken(List<Long> repositoryIds, String accessToken) {
public void changeAccessToken(Long repoId, String accessToken, Principal principal) {
CodeRepo repo = findCodeRepoService.findById(repoId)
.orElseThrow(() -> new CodeRepoNotFoundException("Repository not found"));
permissionFactory.canUserManageTeam(repo.getTeam(), principal);
updateCodeRepoService.changeAccessToken(repo, accessToken);
}

public void bulkChangeAccessToken(List<Long> repositoryIds, String accessToken, Principal principal) {
for (Long repositoryId : repositoryIds) {
CodeRepo repository = findCodeRepoService.findById(repositoryId)
.orElseThrow(() -> new CodeRepoNotFoundException("Repository not found: " + repositoryId));
permissionFactory.canUserManageTeam(repository.getTeam(), principal);
}
updateCodeRepoService.bulkChangeAccessToken(repositoryIds, accessToken);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ void updateRepositoryMetadata(@Param("repoId") Long repoId,
@Query("UPDATE CodeRepo c SET c.accessToken = :accessToken WHERE c.id IN :repositoryIds")
void updateAccessTokenForRepositories(@Param("repositoryIds") List<Long> repositoryIds, @Param("accessToken") String accessToken);

@Modifying
@Query("UPDATE CodeRepo c SET c.accessToken = :accessToken WHERE c.id = :repositoryId")
void updateAccessTokenForRepository(@Param("repositoryId") Long repositoryId, @Param("accessToken") String accessToken);

Optional<CodeRepo> findByRemoteIdAndRepourl(Long id, String repoUrl);

boolean existsByTeamAndNameIgnoreCase(Team team, String trimmed);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
public interface ScanInfoRepository extends CrudRepository<ScanInfo, Long> {

Optional<ScanInfo> findByCodeRepoAndCodeRepoBranchAndCommitId(CodeRepo codeRepo, CodeRepoBranch codeRepoBranch, String commitId);
List<ScanInfo> findAllByCodeRepoAndCodeRepoBranchAndCommitIdOrderByInsertedDateDesc(CodeRepo codeRepo, CodeRepoBranch codeRepoBranch, String commitId);
List<ScanInfo> findByCodeRepo(CodeRepo repo);
boolean existsByCodeRepoBranch(CodeRepoBranch codeRepoBranch);
void deleteByCodeRepo(CodeRepo codeRepo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public Mono<Void> createCodeRepo(CreateCodeRepoRequestDto createCodeRepoRequestD
.forEach(finalCodeRepo::upsertLanguage);

finalCodeRepo = codeRepoRepository.save(finalCodeRepo);
scaService.createDtrackProject(finalCodeRepo);
// scaService.createDtrackProject(finalCodeRepo);
log.info("[CodeRepoService] Creating initial scan for {} default branch {}", codeRepo.getRepourl(), codeRepoBranch.getName());
scanManagerService.scanRepository(finalCodeRepo, finalCodeRepo.getDefaultBranch(), null, null);

Expand All @@ -103,9 +103,11 @@ public Mono<Void> createCodeRepo(CreateCodeRepoRequestDto createCodeRepoRequestD
}
}
else {
// FIX: Uncommented this block to throw an exception when the team is not found or the repo already exists.
log.warn("[CodeRepoService] Trying to add repository that exsits");
//throw new TeamNotFoundException("[CreateCodeRepoService] Team " + createCodeRepoRequestDto.getTeam() + " not found or repo already exists.");
if (team.isEmpty()) {
throw new TeamNotFoundException("[CreateCodeRepoService] Team " + createCodeRepoRequestDto.getTeam() + " not found.");
}
log.warn("[CodeRepoService] Trying to add repository that already exists: {}", repoResponse.getWebUrl());
throw new IllegalArgumentException("Repository already exists.");
}
})
// Schedule the execution of the blocking code on the 'boundedElastic' scheduler.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.checkerframework.checker.units.qual.C;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
Expand Down Expand Up @@ -344,6 +343,17 @@ public void bulkChangeAccessToken(List<Long> repositoryIds, String accessToken)
);
}

@Modifying
@Transactional
public void changeAccessToken(CodeRepo codeRepo, String accessToken) {
if (!StringUtils.hasText(accessToken)) {
throw new IllegalArgumentException("Access token cannot be empty.");
}
String sanitizedToken = accessToken.trim();
codeRepoRepository.updateAccessTokenForRepository(codeRepo.getId(), sanitizedToken);
log.info("Changed access token for repository {}", codeRepo.getRepourl());
}

@Transactional
public void renameCodeRepo(CodeRepo codeRepo, String newName) {
if (!StringUtils.hasText(newName)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
import io.mixeway.mixewayflowapi.db.entity.ScanInfo;
import io.mixeway.mixewayflowapi.db.repository.ScanInfoRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.Optional;
import java.util.List;

/**
* Service class for creating or updating immutable {@link ScanInfo} entities.
Expand All @@ -20,6 +21,7 @@
*/
@Service
@RequiredArgsConstructor
@Log4j2
public class CreateScanInfoService {

private final ScanInfoRepository scanInfoRepository;
Expand Down Expand Up @@ -54,12 +56,16 @@ public ScanInfo createOrUpdateScanInfo(CodeRepo codeRepo, CodeRepoBranch codeRep
int iacHigh, int iacCritical, int secretsHigh, int secretsCritical, int gitlabHigh, int gitlabCritical,
int dastHigh, int dastCritical) {

Optional<ScanInfo> existingScanInfoOpt = scanInfoRepository.findByCodeRepoAndCodeRepoBranchAndCommitId(codeRepo, codeRepoBranch, commitId);

ScanInfo scanInfo;
List<ScanInfo> existingScanInfos = scanInfoRepository
.findAllByCodeRepoAndCodeRepoBranchAndCommitIdOrderByInsertedDateDesc(codeRepo, codeRepoBranch, commitId);

if (existingScanInfoOpt.isPresent()) {
scanInfo = existingScanInfoOpt.get();
if (!existingScanInfos.isEmpty()) {
if (existingScanInfos.size() > 1) {
log.warn("[ScanInfo] Found {} duplicate scan_info rows for repoId={}, branchId={}, commitId={}. Updating latest row only.",
existingScanInfos.size(), codeRepo.getId(), codeRepoBranch.getId(), commitId);
}
scanInfo = existingScanInfos.get(0);
scanInfo.updateScanInfo(scaScanStatus, sastScanStatus, iacScanStatus, secretsScanStatus, gitlabScanStatus, scaHigh, scaCritical,
sastHigh, sastCritical, iacHigh, iacCritical, secretsHigh, secretsCritical, gitlabHigh, gitlabCritical);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import io.mixeway.mixewayflowapi.integrations.repo.apiclient.GitLabApiClientService;
import io.mixeway.mixewayflowapi.integrations.repo.apiclient.GiteaApiClientService;
import io.mixeway.mixewayflowapi.integrations.repo.dto.ImportCodeRepoResponseDto;
import io.mixeway.mixewayflowapi.scanmanager.service.ScanManagerService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
Expand All @@ -31,13 +32,15 @@ public class RepositoryMetadataSyncService {
private final GitHubApiClientService gitHubApiClientService;
private final GiteaApiClientService giteaApiClientService;
private final BitbucketApiClientService bitbucketApiClientService;
private final ScanManagerService scanManagerService;

public void syncAllRepositoriesMetadata() {
codeRepoRepository.findAll().forEach(this::syncRepositoryMetadata);
}

private void syncRepositoryMetadata(CodeRepo codeRepo) {
try {
CodeRepo currentCodeRepo = codeRepo;
RepoMetadata metadata = fetchRepositoryMetadata(codeRepo);
if (metadata == null || !hasText(metadata.name()) || !hasText(metadata.webUrl()) || !hasText(metadata.defaultBranch())) {
return;
Expand All @@ -52,9 +55,10 @@ private void syncRepositoryMetadata(CodeRepo codeRepo) {

if (nameChanged || urlChanged || defaultBranchChanged) {
codeRepoRepository.updateRepositoryMetadata(codeRepo.getId(), normalizedName, metadata.webUrl(), defaultBranch);
currentCodeRepo = codeRepoRepository.findById(codeRepo.getId()).orElse(codeRepo);
}

BranchSyncResult branchSyncResult = syncRepositoryBranches(codeRepo, metadata.defaultBranch(), metadata.webUrl());
BranchSyncResult branchSyncResult = syncRepositoryBranches(currentCodeRepo, metadata.defaultBranch(), metadata.webUrl());
if (nameChanged || urlChanged || defaultBranchChanged || branchSyncResult.hasChanges()) {
log.info(
"Repository metadata sync updated repoId={} remoteId={} [nameChanged={}, urlChanged={}, defaultBranchChanged={}, branchesAdded={}, branchesMarkedExisting={}, branchesMarkedMissing={}]",
Expand All @@ -68,6 +72,12 @@ private void syncRepositoryMetadata(CodeRepo codeRepo) {
branchSyncResult.branchesMarkedMissing()
);
}

if (defaultBranchChanged) {
log.info("Default branch changed for repoId={} (remoteId={}). Triggering scan on branch {}.",
currentCodeRepo.getId(), currentCodeRepo.getRemoteId(), currentCodeRepo.getDefaultBranch().getName());
scanManagerService.scanRepository(currentCodeRepo, currentCodeRepo.getDefaultBranch(), null, null);
}
} catch (Exception e) {
log.warn("Failed to sync metadata for repo {} (id={}): {}", codeRepo.getName(), codeRepo.getId(), e.getMessage());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,19 +72,19 @@ public Optional<Team> findById(Long teamId, Principal principal) {
public void canUserManageTeam(Team team, Principal principal) {
UserInfo userInfo = findUserService.findUser(principal.getName());
UserRole adminRole = findRoleService.findUserRole("ADMIN");
boolean isAdmin = userInfo.getRoles().contains(adminRole);
boolean isTeamMember = userInfo.getTeams().contains(team);

// In SaaS mode, check organization boundaries
if (appConfigService.isSaasMode()) {
if (!userInfo.getRoles().contains(adminRole) ||
(team.getOrganization() != null &&
!userInfo.getOrganizations().contains(team.getOrganization()))) {
throw new UnauthorizedException("");
}
} else {
// STANDALONE mode - existing logic
if (!userInfo.getRoles().contains(adminRole) && !userInfo.getTeams().contains(team)) {
throw new UnauthorizedException("");
}
// Non-admins (including TEAM_MANAGER) can manage only their own teams.
if (!isAdmin && !isTeamMember) {
throw new UnauthorizedException("");
}

// In SaaS mode, also enforce organization boundary for both admin and team members.
if (appConfigService.isSaasMode()
&& team.getOrganization() != null
&& !userInfo.getOrganizations().contains(team.getOrganization())) {
throw new UnauthorizedException("");
}
}
public void canUserAccessTeam(Team team, Principal principal) {
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/app/service/DashboardService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,12 @@ export class DashboardService {
return this.http.put<any>(this.loginUrl + '/api/v1/coderepo/bulk/change-team', {"repositoryIds":repoIds,"newTeamId":newTeamId},{ withCredentials: true });

}

changeAccessTokenForRepos(repoIds: number[], accessToken: string): Observable<any> {
return this.http.post<any>(
`${this.loginUrl}/api/v1/coderepo/bulk/change-token`,
{ repositoryIds: repoIds, accessToken },
{ withCredentials: true }
);
}
}
Loading