Skip to content

Commit

Permalink
feat: Add API for single pull request scan
Browse files Browse the repository at this point in the history
Signed-off-by: Oleg Kopysov <[email protected]>
  • Loading branch information
o-kopysov committed Dec 28, 2023
1 parent 19e32cc commit 1d65e01
Show file tree
Hide file tree
Showing 12 changed files with 254 additions and 174 deletions.
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@
<artifactId>h2</artifactId>
<version>2.2.220</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

</dependencies>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
package com.lpvs.controller;

import com.lpvs.entity.LPVSQueue;
import com.lpvs.entity.enums.LPVSPullRequestAction;
import com.lpvs.repository.LPVSQueueRepository;
import com.lpvs.service.LPVSGitHubService;
import com.lpvs.service.LPVSQueueService;
Expand All @@ -15,17 +16,18 @@
import com.lpvs.entity.LPVSResponseWrapper;
import lombok.extern.slf4j.Slf4j;

import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import org.apache.commons.codec.binary.Hex;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.util.HtmlUtils;

import java.util.Date;
import java.util.Optional;
import javax.annotation.PostConstruct;
Expand All @@ -38,7 +40,7 @@
*/
@RestController
@Slf4j
public class GitHubWebhooksController {
public class GitHubController {

/**
* The GitHub secret used for validating webhook payloads.
Expand Down Expand Up @@ -90,7 +92,7 @@ public void initializeGitHubSecret() {
private static final String ALGORITHM = "HmacSHA256";

/**
* Constructor for GitHubWebhooksController.
* Constructor for GitHubController.
* Initializes LPVSQueueService, LPVSGitHubService, LPVSQueueRepository, GitHub secret, and LPVSExitHandler.
*
* @param queueService LPVSQueueService for handling user-related business logic.
Expand All @@ -99,7 +101,7 @@ public void initializeGitHubSecret() {
* @param GITHUB_SECRET The GitHub secret used for validating webhook payloads.
* @param exitHandler LPVSExitHandler for handling application exit scenarios.
*/
public GitHubWebhooksController(
public GitHubController(
LPVSQueueService queueService,
LPVSGitHubService gitHubService,
LPVSQueueRepository queueRepository,
Expand Down Expand Up @@ -166,6 +168,69 @@ public ResponseEntity<LPVSResponseWrapper> gitHubWebhooks(
.body(new LPVSResponseWrapper(SUCCESS));
}

/**
* Handles a GitHub single scan request.
*
* This endpoint performs a single scan operation based on the GitHub organization, repository,
* and pull request number provided in the path variables. The method validates
* the input parameters and performs necessary security checks.
*
* @param gitHubOrg The GitHub organization name. Must not be empty and should be a valid string.
* @param gitHubRepo The GitHub repository name. Must not be empty and should be a valid string.
* @param prNumber The pull request number. Must be a positive integer greater than or equal to 1.
* @return ResponseEntity with LPVSResponseWrapper containing the result of the scan.
* If successful, returns HTTP 200 OK with the success message.
* If there are validation errors or security issues, returns HTTP 403 FORBIDDEN.
*/
@RequestMapping(
value = "/scan/{gitHubUrl}/{gitHubOrg}/{gitHubRepo}/{prNumber}",
method = RequestMethod.POST)
public ResponseEntity<LPVSResponseWrapper> gitHubSingleScan(
@PathVariable("gitHubUrl") @NotEmpty @Valid String gitHubUrl,
@PathVariable("gitHubOrg") @NotEmpty @Valid String gitHubOrg,
@PathVariable("gitHubRepo") @NotEmpty @Valid String gitHubRepo,
@PathVariable("prNumber") @Min(1) @Valid Integer prNumber)
throws InterruptedException {
log.debug("New GitHub single scan request received");

if (GITHUB_SECRET.trim().isEmpty()) {
log.error("Received empty GITHUB_SECRET");
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.headers(LPVSWebhookUtil.generateSecurityHeaders())
.body(new LPVSResponseWrapper(ERROR));
}

// Validate and sanitize user inputs to prevent XSS attacks
gitHubUrl = HtmlUtils.htmlEscape(gitHubUrl);
gitHubOrg = HtmlUtils.htmlEscape(gitHubOrg);
gitHubRepo = HtmlUtils.htmlEscape(gitHubRepo);

String prUrl =
"https://" + gitHubUrl + "/" + gitHubOrg + "/" + gitHubRepo + "/pull/" + prNumber;
LPVSQueue scanConfig =
gitHubService.getInternalQueueByPullRequest(HtmlUtils.htmlEscape(prUrl));
if (scanConfig == null) {
log.error("Error with connection to GitHub.");
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.headers(LPVSWebhookUtil.generateSecurityHeaders())
.body(new LPVSResponseWrapper(ERROR));
}
scanConfig.setAction(LPVSPullRequestAction.SINGLE_SCAN);
scanConfig.setAttempts(0);
scanConfig.setDate(new Date());
scanConfig.setReviewSystemType("github");
queueRepository.save(scanConfig);
log.debug("Pull request scanning is enabled");
gitHubService.setPendingCheck(scanConfig);
log.debug("Set status to Pending done");
queueService.addFirst(scanConfig);
log.debug("Put Scan config to the queue done");
log.debug("Response sent");
return ResponseEntity.ok()
.headers(LPVSWebhookUtil.generateSecurityHeaders())
.body(new LPVSResponseWrapper(SUCCESS));
}

/**
* Verifies if the signature matches the calculated signature using the GitHub secret.
*
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/com/lpvs/controller/package-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* This package contains the controller classes for handling various aspects of the License Pre-Validation Service (LPVS).
* Controllers in this package manage interactions related to GitHub webhooks, user interfaces, and API endpoints.
* <p>
* - {@link com.lpvs.controller.GitHubWebhooksController}: Manages GitHub webhook events, processes payloads, and interacts
* - {@link com.lpvs.controller.GitHubController}: Manages GitHub webhook events, processes payloads, and interacts
* with LPVS services for queue handling and GitHub operations.
* </p><p>
* - {@link com.lpvs.controller.LPVSWebController}: Controls the web interface and API endpoints for LPVS, including user
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ public enum LPVSPullRequestAction {
/**
* Represents the action of triggering a rescan of a pull request.
*/
RESCAN("rescan");
RESCAN("rescan"),

/**
* Represents the action of triggering a manual single scan of a pull request.
*/
SINGLE_SCAN("single-scan");

/**
* The string representation of the pull request action.
Expand Down Expand Up @@ -77,6 +82,8 @@ public static LPVSPullRequestAction convertFrom(String action) {
return UPDATE;
} else if (action.equals(RESCAN.getPullRequestAction())) {
return RESCAN;
} else if (action.equals(SINGLE_SCAN.getPullRequestAction())) {
return SINGLE_SCAN;
} else {
return null;
}
Expand Down
87 changes: 11 additions & 76 deletions src/main/java/com/lpvs/service/LPVSDetectService.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@
import com.lpvs.util.LPVSFileUtil;

import lombok.extern.slf4j.Slf4j;
import org.kohsuke.github.GHPullRequest;
import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GitHub;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ExitCodeEvent;
Expand All @@ -28,12 +25,10 @@

import javax.annotation.PostConstruct;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
Expand Down Expand Up @@ -63,6 +58,11 @@ public class LPVSDetectService {
*/
private LPVSLicenseService licenseService;

/**
* Service responsible for GitHub connection and operation.
*/
private LPVSGitHubService gitHubService;

/**
* Event publisher for triggering application events.
*/
Expand Down Expand Up @@ -92,17 +92,20 @@ public class LPVSDetectService {
* @param gitHubConnectionService Service for connecting to the GitHub API.
* @param scanossDetectService Service for license detection using ScanOSS.
* @param licenseService Service for license conflict analysis.
* @param gitHubService Service for GitHub connection and operation.
*/
@Autowired
public LPVSDetectService(
@Value("${scanner:scanoss}") String scannerType,
LPVSGitHubConnectionService gitHubConnectionService,
LPVSScanossDetectService scanossDetectService,
LPVSLicenseService licenseService) {
LPVSLicenseService licenseService,
LPVSGitHubService gitHubService) {
this.scannerType = scannerType;
this.gitHubConnectionService = gitHubConnectionService;
this.scanossDetectService = scanossDetectService;
this.licenseService = licenseService;
this.gitHubService = gitHubService;
}

/**
Expand All @@ -122,12 +125,11 @@ public void runOneScan() {
if (trigger != null && !HtmlUtils.htmlEscape(trigger).equals("")) {
try {
LPVSQueue webhookConfig =
this.getInternalQueueByPullRequest(HtmlUtils.htmlEscape(trigger));
gitHubService.getInternalQueueByPullRequest(HtmlUtils.htmlEscape(trigger));

List<LPVSFile> scanResult =
this.runScan(
webhookConfig,
LPVSDetectService.getPathByPullRequest(webhookConfig));
webhookConfig, LPVSFileUtil.getPathByPullRequest(webhookConfig));

List<LPVSLicenseService.Conflict<String, String>> detectedConflicts =
licenseService.findConflicts(webhookConfig, scanResult);
Expand Down Expand Up @@ -162,73 +164,6 @@ public void runOneScan() {
}
}

/**
* Retrieves an LPVSQueue configuration based on the GitHub repository and pull request.
*
* @param repo The GitHub repository.
* @param pR The GitHub pull request.
* @return LPVSQueue configuration for the given GitHub repository and pull request.
*/
private static LPVSQueue getGitHubWebhookConfig(GHRepository repo, GHPullRequest pR) {
LPVSQueue webhookConfig = new LPVSQueue();
webhookConfig.setPullRequestUrl(
pR.getHtmlUrl() != null ? pR.getHtmlUrl().toString() : null);
if (pR.getHead() != null
&& pR.getHead().getRepository() != null
&& pR.getHead().getRepository().getHtmlUrl() != null) {
webhookConfig.setPullRequestFilesUrl(
pR.getHead().getRepository().getHtmlUrl().toString());
} else {
webhookConfig.setPullRequestFilesUrl(webhookConfig.getPullRequestUrl());
}
webhookConfig.setPullRequestAPIUrl(pR.getUrl() != null ? pR.getUrl().toString() : null);
webhookConfig.setRepositoryUrl(
repo.getHtmlUrl() != null ? repo.getHtmlUrl().toString() : null);
webhookConfig.setUserId("Single scan run");
webhookConfig.setHeadCommitSHA(pR.getHead() != null ? pR.getHead().getSha() : null);
return webhookConfig;
}

/**
* Retrieves the LPVSQueue configuration for a given GitHub pull request URL.
*
* @param pullRequest The GitHub pull request URL.
* @return LPVSQueue configuration for the given pull request.
*/
public LPVSQueue getInternalQueueByPullRequest(String pullRequest) {
try {
if (pullRequest == null) return null;
String[] pullRequestSplit = pullRequest.split("/");
if (pullRequestSplit.length < 5) return null;
String pullRequestRepo =
String.join(
"/",
Arrays.asList(pullRequestSplit)
.subList(
pullRequestSplit.length - 4,
pullRequestSplit.length - 2));
int pullRequestNum = Integer.parseInt(pullRequestSplit[pullRequestSplit.length - 1]);
GitHub gitHub = gitHubConnectionService.connectToGitHubApi();
GHRepository repo = gitHub.getRepository(pullRequestRepo);
GHPullRequest pR = repo.getPullRequest(pullRequestNum);
return LPVSDetectService.getGitHubWebhookConfig(repo, pR);
} catch (IOException e) {
log.error("Can't set up github client: " + e);
}
return null;
}

/**
* Retrieves the local directory path for a given LPVSQueue configuration.
*
* @param webhookConfig LPVSQueue configuration.
* @return Local directory path for the given LPVSQueue.
*/
public static String getPathByPullRequest(LPVSQueue webhookConfig) {
if (webhookConfig == null) return null;
return LPVSFileUtil.getLocalDirectoryPath(webhookConfig);
}

/**
* Runs a license scan based on the selected scanner type.
*
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/com/lpvs/service/LPVSGitHubService.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

/**
Expand Down Expand Up @@ -413,4 +414,35 @@ public String getRepositoryLicense(LPVSQueue webhookConfig) {
}
return "Proprietary";
}

/**
* Retrieves the LPVSQueue configuration for a given GitHub pull request URL.
*
* @param pullRequest The GitHub pull request URL.
* @return LPVSQueue configuration for the given pull request.
*/
public LPVSQueue getInternalQueueByPullRequest(String pullRequest) {
try {
if (pullRequest == null) {
return null;
}
String[] pullRequestSplit = pullRequest.split("/");
if (pullRequestSplit.length < 5) return null;
String pullRequestRepo =
String.join(
"/",
Arrays.asList(pullRequestSplit)
.subList(
pullRequestSplit.length - 4,
pullRequestSplit.length - 2));
int pullRequestNum = Integer.parseInt(pullRequestSplit[pullRequestSplit.length - 1]);
GitHub gitHub = gitHubConnectionService.connectToGitHubApi();
GHRepository repo = gitHub.getRepository(pullRequestRepo);
GHPullRequest pR = repo.getPullRequest(pullRequestNum);
return LPVSWebhookUtil.getGitHubWebhookConfig(repo, pR);
} catch (IOException e) {
log.error("Can't set up github client: " + e);
}
return null;
}
}
4 changes: 2 additions & 2 deletions src/main/java/com/lpvs/service/LPVSQueueService.java
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,8 @@ public LPVSQueue getLatestScan(List<LPVSQueue> webhookConfigList) {
public void processWebHook(LPVSQueue webhookConfig) throws IOException {
LPVSPullRequest pullRequest = new LPVSPullRequest();
try {
log.info("GitHub Webhook processing...");
log.info(webhookConfig.toString());
log.info("GitHub queue processing...");
log.debug(webhookConfig.toString());

String filePath = gitHubService.getPullRequestFiles(webhookConfig);

Expand Down
11 changes: 11 additions & 0 deletions src/main/java/com/lpvs/util/LPVSFileUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,15 @@ public static String getScanResultsDirectoryPath(LPVSQueue webhookConfig) {
+ "Results/"
+ LPVSWebhookUtil.getRepositoryName(webhookConfig);
}

/**
* Retrieves the local directory path for a given LPVSQueue configuration.
*
* @param webhookConfig LPVSQueue configuration.
* @return Local directory path for the given LPVSQueue.
*/
public static String getPathByPullRequest(LPVSQueue webhookConfig) {
if (webhookConfig == null) return null;
return getLocalDirectoryPath(webhookConfig);
}
}
Loading

0 comments on commit 1d65e01

Please sign in to comment.