From 1d65e0102753fc2990499febc039db9e9b48cdb5 Mon Sep 17 00:00:00 2001 From: Oleg Kopysov Date: Wed, 27 Dec 2023 14:22:28 +0200 Subject: [PATCH 01/12] feat: Add API for single pull request scan Signed-off-by: Oleg Kopysov --- pom.xml | 4 + ...sController.java => GitHubController.java} | 81 +++++++++++++++-- .../com/lpvs/controller/package-info.java | 2 +- .../entity/enums/LPVSPullRequestAction.java | 9 +- .../com/lpvs/service/LPVSDetectService.java | 87 +++---------------- .../com/lpvs/service/LPVSGitHubService.java | 32 +++++++ .../com/lpvs/service/LPVSQueueService.java | 4 +- src/main/java/com/lpvs/util/LPVSFileUtil.java | 11 +++ .../java/com/lpvs/util/LPVSWebhookUtil.java | 29 +++++++ ...lerTest.java => GitHubControllerTest.java} | 20 ++--- .../lpvs/service/LPVSDetectServiceTest.java | 79 +++++------------ .../lpvs/service/LPVSGitHubServiceTest.java | 70 +++++++++++---- 12 files changed, 254 insertions(+), 174 deletions(-) rename src/main/java/com/lpvs/controller/{GitHubWebhooksController.java => GitHubController.java} (67%) rename src/test/java/com/lpvs/controller/{GitHubWebhooksControllerTest.java => GitHubControllerTest.java} (89%) diff --git a/pom.xml b/pom.xml index 970e4b97..5828e74c 100644 --- a/pom.xml +++ b/pom.xml @@ -136,6 +136,10 @@ h2 2.2.220 + + org.springframework.boot + spring-boot-starter-validation + diff --git a/src/main/java/com/lpvs/controller/GitHubWebhooksController.java b/src/main/java/com/lpvs/controller/GitHubController.java similarity index 67% rename from src/main/java/com/lpvs/controller/GitHubWebhooksController.java rename to src/main/java/com/lpvs/controller/GitHubController.java index 4c7af690..bf09f2b3 100644 --- a/src/main/java/com/lpvs/controller/GitHubWebhooksController.java +++ b/src/main/java/com/lpvs/controller/GitHubController.java @@ -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; @@ -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; @@ -38,7 +40,7 @@ */ @RestController @Slf4j -public class GitHubWebhooksController { +public class GitHubController { /** * The GitHub secret used for validating webhook payloads. @@ -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. @@ -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, @@ -166,6 +168,69 @@ public ResponseEntity 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 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. * diff --git a/src/main/java/com/lpvs/controller/package-info.java b/src/main/java/com/lpvs/controller/package-info.java index 321d8a2e..214570b9 100644 --- a/src/main/java/com/lpvs/controller/package-info.java +++ b/src/main/java/com/lpvs/controller/package-info.java @@ -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. *

- * - {@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. *

* - {@link com.lpvs.controller.LPVSWebController}: Controls the web interface and API endpoints for LPVS, including user diff --git a/src/main/java/com/lpvs/entity/enums/LPVSPullRequestAction.java b/src/main/java/com/lpvs/entity/enums/LPVSPullRequestAction.java index 2ead8219..d73bbe43 100644 --- a/src/main/java/com/lpvs/entity/enums/LPVSPullRequestAction.java +++ b/src/main/java/com/lpvs/entity/enums/LPVSPullRequestAction.java @@ -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. @@ -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; } diff --git a/src/main/java/com/lpvs/service/LPVSDetectService.java b/src/main/java/com/lpvs/service/LPVSDetectService.java index 98cf9bd6..3edc31cd 100644 --- a/src/main/java/com/lpvs/service/LPVSDetectService.java +++ b/src/main/java/com/lpvs/service/LPVSDetectService.java @@ -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; @@ -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; /** @@ -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. */ @@ -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; } /** @@ -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 scanResult = this.runScan( - webhookConfig, - LPVSDetectService.getPathByPullRequest(webhookConfig)); + webhookConfig, LPVSFileUtil.getPathByPullRequest(webhookConfig)); List> detectedConflicts = licenseService.findConflicts(webhookConfig, scanResult); @@ -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. * diff --git a/src/main/java/com/lpvs/service/LPVSGitHubService.java b/src/main/java/com/lpvs/service/LPVSGitHubService.java index 236d656a..7c2f9687 100644 --- a/src/main/java/com/lpvs/service/LPVSGitHubService.java +++ b/src/main/java/com/lpvs/service/LPVSGitHubService.java @@ -28,6 +28,7 @@ import org.springframework.stereotype.Service; import java.io.IOException; +import java.util.Arrays; import java.util.List; /** @@ -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; + } } diff --git a/src/main/java/com/lpvs/service/LPVSQueueService.java b/src/main/java/com/lpvs/service/LPVSQueueService.java index 5847cfb8..beb0ebb6 100644 --- a/src/main/java/com/lpvs/service/LPVSQueueService.java +++ b/src/main/java/com/lpvs/service/LPVSQueueService.java @@ -214,8 +214,8 @@ public LPVSQueue getLatestScan(List 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); diff --git a/src/main/java/com/lpvs/util/LPVSFileUtil.java b/src/main/java/com/lpvs/util/LPVSFileUtil.java index 64db0ae1..7fe1643c 100644 --- a/src/main/java/com/lpvs/util/LPVSFileUtil.java +++ b/src/main/java/com/lpvs/util/LPVSFileUtil.java @@ -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); + } } diff --git a/src/main/java/com/lpvs/util/LPVSWebhookUtil.java b/src/main/java/com/lpvs/util/LPVSWebhookUtil.java index a63b74c5..0e049dcd 100644 --- a/src/main/java/com/lpvs/util/LPVSWebhookUtil.java +++ b/src/main/java/com/lpvs/util/LPVSWebhookUtil.java @@ -11,6 +11,8 @@ import com.lpvs.entity.LPVSQueue; import com.lpvs.entity.enums.LPVSPullRequestAction; import lombok.extern.slf4j.Slf4j; +import org.kohsuke.github.GHPullRequest; +import org.kohsuke.github.GHRepository; import org.springframework.http.HttpHeaders; import java.util.Arrays; @@ -231,4 +233,31 @@ public static HttpHeaders generateSecurityHeaders() { return headers; } + + /** + * 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. + */ + public 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; + } } diff --git a/src/test/java/com/lpvs/controller/GitHubWebhooksControllerTest.java b/src/test/java/com/lpvs/controller/GitHubControllerTest.java similarity index 89% rename from src/test/java/com/lpvs/controller/GitHubWebhooksControllerTest.java rename to src/test/java/com/lpvs/controller/GitHubControllerTest.java index d8f8ed8d..1b4189a0 100644 --- a/src/test/java/com/lpvs/controller/GitHubWebhooksControllerTest.java +++ b/src/test/java/com/lpvs/controller/GitHubControllerTest.java @@ -23,7 +23,7 @@ import static org.mockito.Mockito.mock; @Slf4j -public class GitHubWebhooksControllerTest { +public class GitHubControllerTest { private static final String SIGNATURE = "X-Hub-Signature-256"; private static final String SUCCESS = "Success"; @@ -34,8 +34,8 @@ public class GitHubWebhooksControllerTest { LPVSQueueService mocked_instance_queueServ = mock(LPVSQueueService.class); LPVSGitHubService mocked_instance_ghServ = mock(LPVSGitHubService.class); LPVSQueueRepository mocked_queueRepo = mock(LPVSQueueRepository.class); - GitHubWebhooksController gitHubWebhooksController = - new GitHubWebhooksController( + GitHubController gitHubController = + new GitHubController( mocked_instance_queueServ, mocked_instance_ghServ, mocked_queueRepo, @@ -46,7 +46,7 @@ public class GitHubWebhooksControllerTest { public void noSignatureTest() { ResponseEntity actual; try { - actual = gitHubWebhooksController.gitHubWebhooks(null, null); + actual = gitHubController.gitHubWebhooks(null, null); } catch (Exception e) { actual = null; } @@ -59,7 +59,7 @@ public void noSignatureTest() { public void noPayloadTest() { ResponseEntity actual; try { - actual = gitHubWebhooksController.gitHubWebhooks(SIGNATURE, null); + actual = gitHubController.gitHubWebhooks(SIGNATURE, null); } catch (Exception e) { actual = null; } @@ -109,7 +109,7 @@ public void okTest() { + "}"; try { - actual = gitHubWebhooksController.gitHubWebhooks(SIGNATURE, json_to_test); + actual = gitHubController.gitHubWebhooks(SIGNATURE, json_to_test); } catch (Exception e) { log.error(e.getMessage()); actual = null; @@ -148,13 +148,13 @@ public void wrongSecretTest() { + "}" + "}"; try { - gitHubWebhooksController.initializeGitHubSecret(); - boolean secret = gitHubWebhooksController.wrongSecret(signature, json_to_test); + gitHubController.initializeGitHubSecret(); + boolean secret = gitHubController.wrongSecret(signature, json_to_test); assertEquals(secret, false); - secret = gitHubWebhooksController.wrongSecret(signature + " ", json_to_test); + secret = gitHubController.wrongSecret(signature + " ", json_to_test); assertEquals(secret, true); } catch (Exception e) { - log.error("GitHubWebhooksControllerTest::wrongSecretTest exception: " + e); + log.error("GitHubControllerTest::wrongSecretTest exception: " + e); fail(); } } diff --git a/src/test/java/com/lpvs/service/LPVSDetectServiceTest.java b/src/test/java/com/lpvs/service/LPVSDetectServiceTest.java index fcfe3b41..907e77ea 100644 --- a/src/test/java/com/lpvs/service/LPVSDetectServiceTest.java +++ b/src/test/java/com/lpvs/service/LPVSDetectServiceTest.java @@ -11,6 +11,7 @@ import com.lpvs.service.scanner.scanoss.LPVSScanossDetectService; import com.lpvs.util.LPVSCommentUtil; +import com.lpvs.util.LPVSFileUtil; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeEach; @@ -24,8 +25,6 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.boot.context.event.ApplicationReadyEvent; -import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEventPublisher; import java.io.IOException; @@ -39,7 +38,6 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -51,25 +49,14 @@ public class LPVSDetectServiceTest { @Mock private ApplicationEventPublisher mockEventPublisher; - @Mock private LPVSGitHubConnectionService gitHubConnectionService; - - @Mock private GitHub gitHub; - - @Mock private GHRepository ghRepository; - - @Mock private GHPullRequest ghPullRequest; - @Mock private LPVSScanossDetectService scanossDetectService; - @Mock private ApplicationContext applicationContext; - - @Mock private ApplicationReadyEvent applicationReadyEvent; - @InjectMocks private LPVSDetectService lpvsDetectService; @Nested class TestInit { - final LPVSDetectService detectService = new LPVSDetectService("scanoss", null, null, null); + final LPVSDetectService detectService = + new LPVSDetectService("scanoss", null, null, null, null); @Test public void testInit() { @@ -90,6 +77,7 @@ class TestRunScan__Scanoss { LPVSGitHubConnectionService github_mock = mock(LPVSGitHubConnectionService.class); LPVSScanossDetectService scanoss_mock = mock(LPVSScanossDetectService.class); LPVSLicenseService licenseservice_mock = mock(LPVSLicenseService.class); + LPVSGitHubService githubservice_mock = mock(LPVSGitHubService.class); GitHub mockGitHub = mock(GitHub.class); GHCommitPointer mockCommitPointer = mock(GHCommitPointer.class); GHRepository mockRepository = mock(GHRepository.class); @@ -105,7 +93,11 @@ class TestRunScan__Scanoss { void setUp() throws IOException { detectService = new LPVSDetectService( - "scanoss", github_mock, scanoss_mock, licenseservice_mock); + "scanoss", + github_mock, + scanoss_mock, + licenseservice_mock, + githubservice_mock); webhookConfig = new LPVSQueue(); webhookConfig.setId(1L); @@ -127,7 +119,7 @@ void setUp() throws IOException { @Test void testRunOneScanWithNullTriger() throws NoSuchFieldException, IllegalAccessException { lpvsDetectService = - spy(new LPVSDetectService("scanoss", null, scanossDetectService, null)); + spy(new LPVSDetectService("scanoss", null, scanossDetectService, null, null)); setPrivateField(lpvsDetectService, "trigger", null); setPrivateField(lpvsDetectService, "eventPublisher", mockEventPublisher); @@ -143,7 +135,7 @@ void testRunOneScanWithNullTriger() throws NoSuchFieldException, IllegalAccessEx void testRunOneScan_Default() throws NoSuchFieldException, IllegalAccessException { lpvsDetectService = - spy(new LPVSDetectService("scanoss", null, scanossDetectService, null)); + spy(new LPVSDetectService("scanoss", null, scanossDetectService, null, null)); setPrivateField(lpvsDetectService, "trigger", "fake-trigger-value"); setPrivateField(lpvsDetectService, "eventPublisher", mockEventPublisher); @@ -162,7 +154,7 @@ void testRunOneScan_Branch2() List.of(conflict_1, conflict_1); lpvsDetectService = - spy(new LPVSDetectService("scanoss", null, scanossDetectService, null)); + spy(new LPVSDetectService("scanoss", null, scanossDetectService, null, null)); // Mock the necessary GitHub objects for LPVSQueue when(mockGitHub.getRepository(any())).thenReturn(mockRepository); @@ -204,7 +196,7 @@ void testRunOneScan_Branch3() doNothing().when(mockEventPublisher).publishEvent(any()); lpvsDetectService = - spy(new LPVSDetectService("scanoss", null, scanossDetectService, null)); + spy(new LPVSDetectService("scanoss", null, scanossDetectService, null, null)); setPrivateField(detectService, "trigger", "github/owner/repo/branch/123"); setPrivateField(detectService, "scannerType", "scanoss"); @@ -329,7 +321,6 @@ void testRunOneScan_TriggerNotNull_Branch3() throws Exception { when(mockPullRequest.getHead()).thenReturn(mockCommitPointer); when(licenseservice_mock.findConflicts(webhookConfig, null)).thenReturn(expected); when(mockCommitPointer.getRepository()).thenReturn(mockHeadRepository2); - when(mockHeadRepository2.getHtmlUrl()).thenReturn(null); // Set up expected values String expectedPullRequestUrl = "https://example.com/pull/1"; @@ -361,45 +352,12 @@ void testCommentBuilder_ConflictFilePresent() throws Exception { assertNotNull(commentHTML); } - @Test - void testGetInternalQueueByPullRequestWithNull() throws IOException { - LPVSQueue result = lpvsDetectService.getInternalQueueByPullRequest(null); - assertNull(result); - } - - @Test - void testGetInternalQueueByPullRequest() throws IOException { - String pullRequest = "github/owner/repo/branch/123"; - when(gitHubConnectionService.connectToGitHubApi()).thenReturn(gitHub); - when(gitHub.getRepository("owner/repo")).thenReturn(ghRepository); - when(ghRepository.getPullRequest(123)).thenReturn(ghPullRequest); - - LPVSQueue result = lpvsDetectService.getInternalQueueByPullRequest(pullRequest); - - assertNotNull(result); - assertEquals(result.getUserId(), "Single scan run"); - } - - @Test - void testGetInternalQueueByPullRequestError() throws IOException { - String pullRequest = "github/owner/repo/branch/123"; - - when(gitHubConnectionService.connectToGitHubApi()).thenThrow(IOException.class); - - try { - LPVSQueue result = lpvsDetectService.getInternalQueueByPullRequest(pullRequest); - assertNull(result, "Expected result to be null"); - } catch (Exception e) { - fail("Exception not expected to be thrown here"); - } - } - @Test public void testGetPathByPullRequest() { LPVSQueue mockWebhookConfig = mock(LPVSQueue.class); - String result = LPVSDetectService.getPathByPullRequest(mockWebhookConfig); + String result = LPVSFileUtil.getPathByPullRequest(mockWebhookConfig); assertNotNull(result); } @@ -434,6 +392,7 @@ class TestRunScan__ScanossException { LPVSGitHubConnectionService github_mock = mock(LPVSGitHubConnectionService.class); LPVSScanossDetectService scanoss_mock = mock(LPVSScanossDetectService.class); LPVSLicenseService licenseservice_mock = mock(LPVSLicenseService.class); + LPVSGitHubService githubservice_mock = mock(LPVSGitHubService.class); LPVSQueue webhookConfig; final String test_path = "test_path"; @@ -443,7 +402,11 @@ class TestRunScan__ScanossException { void setUp() { detectService = new LPVSDetectService( - "scanoss", github_mock, scanoss_mock, licenseservice_mock); + "scanoss", + github_mock, + scanoss_mock, + licenseservice_mock, + githubservice_mock); webhookConfig = new LPVSQueue(); webhookConfig.setId(1L); @@ -488,7 +451,7 @@ class TestRunScan__NotScanoss { @BeforeEach void setUp() { - detectService = new LPVSDetectService("not_scanoss", null, null, null); + detectService = new LPVSDetectService("not_scanoss", null, null, null, null); } @Test diff --git a/src/test/java/com/lpvs/service/LPVSGitHubServiceTest.java b/src/test/java/com/lpvs/service/LPVSGitHubServiceTest.java index 1f62098b..ddaa6694 100644 --- a/src/test/java/com/lpvs/service/LPVSGitHubServiceTest.java +++ b/src/test/java/com/lpvs/service/LPVSGitHubServiceTest.java @@ -24,11 +24,11 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.junitpioneer.jupiter.SetEnvironmentVariable; import org.kohsuke.github.*; -import org.mockito.MockedStatic; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; import java.io.IOException; @@ -48,6 +48,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; @Slf4j +@ExtendWith(MockitoExtension.class) public class LPVSGitHubServiceTest { /** @@ -60,6 +61,16 @@ public class LPVSGitHubServiceTest { */ private LPVSExitHandler exitHandler; + @Mock private GitHub gitHub; + + @Mock private GHRepository ghRepository; + + @Mock private GHPullRequest ghPullRequest; + + @Mock private LPVSGitHubConnectionService gitHubConnectionService; + + @InjectMocks private LPVSGitHubService gitHubService; + static class GHPullRequestOurMock extends GHPullRequest { private final URL mockedGetUrl; private final String mockedGetTitle; @@ -4316,25 +4327,10 @@ public void testCheckNotEmpty() String GH_LOGIN = ""; String GH_AUTH_TOKEN = "non-empty"; String GH_API_URL = ""; - LPVSPullRequestRepository mocked_pullRequestRepository = - mock(LPVSPullRequestRepository.class); - LPVSDetectedLicenseRepository mocked_lpvsDetectedLicenseRepository = - mock(LPVSDetectedLicenseRepository.class); - LPVSLicenseRepository mocked_lpvsLicenseRepository = mock(LPVSLicenseRepository.class); - LPVSLicenseConflictRepository mocked_lpvsLicenseConflictRepository = - mock(LPVSLicenseConflictRepository.class); LPVSExitHandler exitHandler = mock(LPVSExitHandler.class); LPVSGitHubConnectionService lpvsGitHubConnectionService = new LPVSGitHubConnectionService( GH_LOGIN, GH_AUTH_TOKEN, GH_API_URL, exitHandler); - - final LPVSGitHubService gh_service = - new LPVSGitHubService( - mocked_pullRequestRepository, - mocked_lpvsDetectedLicenseRepository, - mocked_lpvsLicenseRepository, - mocked_lpvsLicenseConflictRepository, - lpvsGitHubConnectionService); Method method = lpvsGitHubConnectionService.getClass().getDeclaredMethod("checks"); method.setAccessible(true); method.invoke(lpvsGitHubConnectionService); @@ -4342,4 +4338,42 @@ public void testCheckNotEmpty() verify(exitHandler, never()).exit(anyInt()); } } + + @Nested + class getInternalQueueByPullRequests { + + @Test + void testGetInternalQueueByPullRequestWithNull() { + LPVSQueue result = gitHubService.getInternalQueueByPullRequest(null); + assertNull(result); + } + + @Test + void testGetInternalQueueByPullRequest() throws IOException { + String pullRequest = "github/owner/repo/branch/123"; + + when(gitHubConnectionService.connectToGitHubApi()).thenReturn(gitHub); + when(gitHub.getRepository("owner/repo")).thenReturn(ghRepository); + when(ghRepository.getPullRequest(123)).thenReturn(ghPullRequest); + + LPVSQueue result = gitHubService.getInternalQueueByPullRequest(pullRequest); + + assertNotNull(result); + assertEquals(result.getUserId(), "Single scan run"); + } + + @Test + void testGetInternalQueueByPullRequestError() throws IOException { + String pullRequest = "github/owner/repo/branch/123"; + + when(gitHubConnectionService.connectToGitHubApi()).thenThrow(IOException.class); + + try { + LPVSQueue result = gitHubService.getInternalQueueByPullRequest(pullRequest); + assertNull(result, "Expected result to be null"); + } catch (Exception e) { + fail("Exception not expected to be thrown here"); + } + } + } } From 048b5293e8d8c766134904dd9ae5063d8c991b79 Mon Sep 17 00:00:00 2001 From: Oleg Kopysov Date: Thu, 28 Dec 2023 14:30:58 +0200 Subject: [PATCH 02/12] docs: Add new endpoint description in YAML file Signed-off-by: Oleg Kopysov --- README.md | 3 ++ doc/lpvs-api.yaml | 47 ++++++++++++++++++- .../com/lpvs/controller/GitHubController.java | 9 ++-- 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index be441111..99b00bc2 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,9 @@ To enable _LPVS_ license scanning for your project, you need to set up GitHub We Configuration from your project side is now complete! +Alternatively, you can use the Pull Request Single Scan API to analyze the code of a specific pull request. +Please refer to the [API Documentation](doc/lpvs-api.yaml) for more information. + --- ### 2. Using pre-built LPVS Docker images diff --git a/doc/lpvs-api.yaml b/doc/lpvs-api.yaml index e571b2e9..e1a31a57 100644 --- a/doc/lpvs-api.yaml +++ b/doc/lpvs-api.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: LPVS API - version: v2-20231124 + version: v2-20231228 description: >- License Pre-Validation Service (LPVS) is a tool designed to proactively manage license-related risks in Open Source code. It conducts in-depth analysis of your @@ -48,6 +48,51 @@ paths: schema: $ref: '#/components/schemas/WebhookResponseForbidden' + /scan/{gitHubDomain}/{gitHubOrg}/{gitHubRepo}/{prNumber}: + post: + tags: + - GitHub Pull Request Single Scan API + summary: GitHub Pull Request Single Scan + description: Endpoint for performing a single scan operation based on GitHub organization, repository, and pull request number. + parameters: + - in: path + name: gitHubDomain + required: true + schema: + type: string + description: GitHub domain name + - in: path + name: gitHubOrg + required: true + schema: + type: string + description: GitHub organization name + - in: path + name: gitHubRepo + required: true + schema: + type: string + description: GitHub repository name + - in: path + name: prNumber + required: true + schema: + type: integer + description: Pull request number + responses: + '200': + description: 200 OK + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookResponseOK' + '403': + description: 403 Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookResponseForbidden' + /api/v1/web/user/login: get: tags: diff --git a/src/main/java/com/lpvs/controller/GitHubController.java b/src/main/java/com/lpvs/controller/GitHubController.java index bf09f2b3..ca0dd5d5 100644 --- a/src/main/java/com/lpvs/controller/GitHubController.java +++ b/src/main/java/com/lpvs/controller/GitHubController.java @@ -175,6 +175,7 @@ public ResponseEntity gitHubWebhooks( * and pull request number provided in the path variables. The method validates * the input parameters and performs necessary security checks. * + * @param gitHubDomain The GitHub domain name. * @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. @@ -183,10 +184,10 @@ public ResponseEntity gitHubWebhooks( * If there are validation errors or security issues, returns HTTP 403 FORBIDDEN. */ @RequestMapping( - value = "/scan/{gitHubUrl}/{gitHubOrg}/{gitHubRepo}/{prNumber}", + value = "/scan/{gitHubDomain}/{gitHubOrg}/{gitHubRepo}/{prNumber}", method = RequestMethod.POST) public ResponseEntity gitHubSingleScan( - @PathVariable("gitHubUrl") @NotEmpty @Valid String gitHubUrl, + @PathVariable("gitHubDomain") @NotEmpty @Valid String gitHubDomain, @PathVariable("gitHubOrg") @NotEmpty @Valid String gitHubOrg, @PathVariable("gitHubRepo") @NotEmpty @Valid String gitHubRepo, @PathVariable("prNumber") @Min(1) @Valid Integer prNumber) @@ -201,12 +202,12 @@ public ResponseEntity gitHubSingleScan( } // Validate and sanitize user inputs to prevent XSS attacks - gitHubUrl = HtmlUtils.htmlEscape(gitHubUrl); + gitHubDomain = HtmlUtils.htmlEscape(gitHubDomain); gitHubOrg = HtmlUtils.htmlEscape(gitHubOrg); gitHubRepo = HtmlUtils.htmlEscape(gitHubRepo); String prUrl = - "https://" + gitHubUrl + "/" + gitHubOrg + "/" + gitHubRepo + "/pull/" + prNumber; + "https://" + gitHubDomain + "/" + gitHubOrg + "/" + gitHubRepo + "/pull/" + prNumber; LPVSQueue scanConfig = gitHubService.getInternalQueueByPullRequest(HtmlUtils.htmlEscape(prUrl)); if (scanConfig == null) { From 7b5cbe731fe599f01cd6763e7dd342daf8d62433 Mon Sep 17 00:00:00 2001 From: Oleg Kopysov Date: Thu, 28 Dec 2023 15:09:27 +0200 Subject: [PATCH 03/12] test: Cover new endpoint by unit tests Signed-off-by: Oleg Kopysov --- doc/lpvs-api.yaml | 4 ++ .../com/lpvs/controller/GitHubController.java | 9 +++- .../lpvs/controller/GitHubControllerTest.java | 44 ++++++++++++++++++- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/doc/lpvs-api.yaml b/doc/lpvs-api.yaml index e1a31a57..c7266df7 100644 --- a/doc/lpvs-api.yaml +++ b/doc/lpvs-api.yaml @@ -61,24 +61,28 @@ paths: schema: type: string description: GitHub domain name + example: 'github.com' - in: path name: gitHubOrg required: true schema: type: string description: GitHub organization name + example: 'Samsung' - in: path name: gitHubRepo required: true schema: type: string description: GitHub repository name + example: 'LPVS' - in: path name: prNumber required: true schema: type: integer description: Pull request number + example: 100 responses: '200': description: 200 OK diff --git a/src/main/java/com/lpvs/controller/GitHubController.java b/src/main/java/com/lpvs/controller/GitHubController.java index ca0dd5d5..57f6889a 100644 --- a/src/main/java/com/lpvs/controller/GitHubController.java +++ b/src/main/java/com/lpvs/controller/GitHubController.java @@ -207,7 +207,14 @@ public ResponseEntity gitHubSingleScan( gitHubRepo = HtmlUtils.htmlEscape(gitHubRepo); String prUrl = - "https://" + gitHubDomain + "/" + gitHubOrg + "/" + gitHubRepo + "/pull/" + prNumber; + "https://" + + gitHubDomain + + "/" + + gitHubOrg + + "/" + + gitHubRepo + + "/pull/" + + prNumber; LPVSQueue scanConfig = gitHubService.getInternalQueueByPullRequest(HtmlUtils.htmlEscape(prUrl)); if (scanConfig == null) { diff --git a/src/test/java/com/lpvs/controller/GitHubControllerTest.java b/src/test/java/com/lpvs/controller/GitHubControllerTest.java index 1b4189a0..c1d95438 100644 --- a/src/test/java/com/lpvs/controller/GitHubControllerTest.java +++ b/src/test/java/com/lpvs/controller/GitHubControllerTest.java @@ -6,6 +6,7 @@ */ package com.lpvs.controller; +import com.lpvs.entity.LPVSQueue; import com.lpvs.entity.LPVSResponseWrapper; import com.lpvs.repository.LPVSQueueRepository; import com.lpvs.service.LPVSGitHubService; @@ -18,9 +19,11 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import java.lang.reflect.Method; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.*; @Slf4j public class GitHubControllerTest { @@ -71,7 +74,6 @@ public void noPayloadTest() { @Test public void okTest() { ResponseEntity actual; - LPVSQueueRepository queueRepository; String json_to_test = "{" @@ -158,4 +160,42 @@ public void wrongSecretTest() { fail(); } } + + @Test + @SetEnvironmentVariable(key = "LPVS_GITHUB_SECRET", value = "LPVS") + public void testGitHubSingleScan_Success() throws Exception { + Method method = gitHubController.getClass().getDeclaredMethod("initializeGitHubSecret"); + method.setAccessible(true); + method.invoke(gitHubController); + LPVSQueue mockScanConfig = new LPVSQueue(); + when(mocked_instance_ghServ.getInternalQueueByPullRequest(anyString())) + .thenReturn(mockScanConfig); + when(mocked_queueRepo.save(any())).thenReturn(mockScanConfig); + doNothing().when(mocked_instance_queueServ).addFirst(any()); + ResponseEntity responseEntity = + gitHubController.gitHubSingleScan("github.com", "org", "repo", 1); + + assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); + } + + @Test + @SetEnvironmentVariable(key = "LPVS_GITHUB_SECRET", value = "LPVS") + public void testGitHubSingleScan_InvalidSecret() throws Exception { + when(mocked_instance_ghServ.getInternalQueueByPullRequest(anyString())).thenReturn(null); + ResponseEntity responseEntity = + gitHubController.gitHubSingleScan("", "org", "repo", 1); + + assertEquals(HttpStatus.FORBIDDEN, responseEntity.getStatusCode()); + } + + @Test + @SetEnvironmentVariable(key = "LPVS_GITHUB_SECRET", value = "LPVS") + public void testGitHubSingleScan_ConnectionError() throws Exception { + when(mocked_instance_ghServ.getInternalQueueByPullRequest(anyString())) + .thenThrow(new RuntimeException("Connection error")); + ResponseEntity responseEntity = + gitHubController.gitHubSingleScan("github.com", "org", "repo", 1); + + assertEquals(HttpStatus.FORBIDDEN, responseEntity.getStatusCode()); + } } From dcf0151eda5d7ea7229d1bca3efeaa6737fd1476 Mon Sep 17 00:00:00 2001 From: Oleg Kopysov Date: Thu, 28 Dec 2023 15:32:45 +0200 Subject: [PATCH 04/12] test: Update unit tests for LPVSPullRequestAction Signed-off-by: Oleg Kopysov --- .../com/lpvs/entity/enums/LPVSPullRequestActionTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/test/java/com/lpvs/entity/enums/LPVSPullRequestActionTest.java b/src/test/java/com/lpvs/entity/enums/LPVSPullRequestActionTest.java index 8dae36b5..d2973d73 100644 --- a/src/test/java/com/lpvs/entity/enums/LPVSPullRequestActionTest.java +++ b/src/test/java/com/lpvs/entity/enums/LPVSPullRequestActionTest.java @@ -20,6 +20,9 @@ public void testConvertFrom() { assertEquals( LPVSPullRequestAction.convertFrom("synchronize"), LPVSPullRequestAction.UPDATE); assertEquals(LPVSPullRequestAction.convertFrom("rescan"), LPVSPullRequestAction.RESCAN); + assertEquals( + LPVSPullRequestAction.convertFrom("single-scan"), + LPVSPullRequestAction.SINGLE_SCAN); assertNotEquals( LPVSPullRequestAction.convertFrom("random_name"), LPVSPullRequestAction.OPEN); @@ -31,6 +34,9 @@ public void testConvertFrom() { LPVSPullRequestAction.convertFrom("random_name"), LPVSPullRequestAction.UPDATE); assertNotEquals( LPVSPullRequestAction.convertFrom("random_name"), LPVSPullRequestAction.RESCAN); + assertNotEquals( + LPVSPullRequestAction.convertFrom("random_name"), + LPVSPullRequestAction.SINGLE_SCAN); assertNull(LPVSPullRequestAction.convertFrom("random_name")); } @@ -42,5 +48,6 @@ public void testGetPullRequestAction() { assertEquals(LPVSPullRequestAction.CLOSE.getPullRequestAction(), "closed"); assertEquals(LPVSPullRequestAction.UPDATE.getPullRequestAction(), "synchronize"); assertEquals(LPVSPullRequestAction.RESCAN.getPullRequestAction(), "rescan"); + assertEquals(LPVSPullRequestAction.SINGLE_SCAN.getPullRequestAction(), "single-scan"); } } From 4da2e82fb79183a6bf798ceb018976c895b45a8f Mon Sep 17 00:00:00 2001 From: Oleg Kopysov Date: Tue, 2 Jan 2024 13:19:02 +0200 Subject: [PATCH 05/12] fix: Update endpoint based on the code review comments Signed-off-by: Oleg Kopysov --- doc/lpvs-api.yaml | 9 +---- .../com/lpvs/controller/GitHubController.java | 40 ++++++++++++++++--- .../lpvs/controller/GitHubControllerTest.java | 28 ++++++++++--- 3 files changed, 58 insertions(+), 19 deletions(-) diff --git a/doc/lpvs-api.yaml b/doc/lpvs-api.yaml index c7266df7..34b67c8e 100644 --- a/doc/lpvs-api.yaml +++ b/doc/lpvs-api.yaml @@ -48,20 +48,13 @@ paths: schema: $ref: '#/components/schemas/WebhookResponseForbidden' - /scan/{gitHubDomain}/{gitHubOrg}/{gitHubRepo}/{prNumber}: + /scan/{gitHubOrg}/{gitHubRepo}/{prNumber}: post: tags: - GitHub Pull Request Single Scan API summary: GitHub Pull Request Single Scan description: Endpoint for performing a single scan operation based on GitHub organization, repository, and pull request number. parameters: - - in: path - name: gitHubDomain - required: true - schema: - type: string - description: GitHub domain name - example: 'github.com' - in: path name: gitHubOrg required: true diff --git a/src/main/java/com/lpvs/controller/GitHubController.java b/src/main/java/com/lpvs/controller/GitHubController.java index 57f6889a..5491815f 100644 --- a/src/main/java/com/lpvs/controller/GitHubController.java +++ b/src/main/java/com/lpvs/controller/GitHubController.java @@ -35,7 +35,7 @@ import javax.crypto.spec.SecretKeySpec; /** - * Controller class for handling GitHub webhook events. + * Controller class for handling GitHub webhook events and single scan requests. * This class is responsible for processing GitHub webhook payloads and triggering relevant actions. */ @RestController @@ -48,12 +48,18 @@ public class GitHubController { */ private String GITHUB_SECRET; + /** + * Name of the GitHub API URL. + * It is set from the LPVS_GITHUB_API_URL environment variable or the application property. + */ + private String GITHUB_API_URL; + /** * Initializes the GitHub secret from the LPVS_GITHUB_SECRET environment variable or the application property. * Exits the application if the secret is not set. */ @PostConstruct - public void initializeGitHubSecret() { + public void initializeGitHubController() { this.GITHUB_SECRET = Optional.ofNullable(this.GITHUB_SECRET) .filter(s -> !s.isEmpty()) @@ -64,6 +70,17 @@ public void initializeGitHubSecret() { log.error("LPVS_GITHUB_SECRET (github.secret) is not set."); exitHandler.exit(-1); } + + this.GITHUB_API_URL = + Optional.ofNullable(this.GITHUB_API_URL) + .filter(s -> !s.isEmpty()) + .orElse( + Optional.ofNullable(System.getenv("LPVS_GITHUB_API_URL")) + .orElse("")); + if (this.GITHUB_API_URL.isEmpty()) { + log.info( + "LPVS_GITHUB_API_URL (github.api.url) is not set. Default domain \"github.com\" will be used."); + } } /** @@ -175,7 +192,6 @@ public ResponseEntity gitHubWebhooks( * and pull request number provided in the path variables. The method validates * the input parameters and performs necessary security checks. * - * @param gitHubDomain The GitHub domain name. * @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. @@ -184,10 +200,9 @@ public ResponseEntity gitHubWebhooks( * If there are validation errors or security issues, returns HTTP 403 FORBIDDEN. */ @RequestMapping( - value = "/scan/{gitHubDomain}/{gitHubOrg}/{gitHubRepo}/{prNumber}", + value = "/scan/{gitHubOrg}/{gitHubRepo}/{prNumber}", method = RequestMethod.POST) public ResponseEntity gitHubSingleScan( - @PathVariable("gitHubDomain") @NotEmpty @Valid String gitHubDomain, @PathVariable("gitHubOrg") @NotEmpty @Valid String gitHubOrg, @PathVariable("gitHubRepo") @NotEmpty @Valid String gitHubRepo, @PathVariable("prNumber") @Min(1) @Valid Integer prNumber) @@ -202,10 +217,23 @@ public ResponseEntity gitHubSingleScan( } // Validate and sanitize user inputs to prevent XSS attacks - gitHubDomain = HtmlUtils.htmlEscape(gitHubDomain); gitHubOrg = HtmlUtils.htmlEscape(gitHubOrg); gitHubRepo = HtmlUtils.htmlEscape(gitHubRepo); + String gitHubDomain; + if (!GITHUB_API_URL.isEmpty()) { + gitHubDomain = + GITHUB_API_URL + .trim() + .substring( + GITHUB_API_URL.indexOf("https://api.") + + "https://api.".length()) + .replaceAll("/", ""); + } else { + gitHubDomain = "github.com"; + } + log.debug("GitHub domain name is \"" + GITHUB_API_URL + "\""); + String prUrl = "https://" + gitHubDomain diff --git a/src/test/java/com/lpvs/controller/GitHubControllerTest.java b/src/test/java/com/lpvs/controller/GitHubControllerTest.java index c1d95438..828b1f1e 100644 --- a/src/test/java/com/lpvs/controller/GitHubControllerTest.java +++ b/src/test/java/com/lpvs/controller/GitHubControllerTest.java @@ -150,7 +150,7 @@ public void wrongSecretTest() { + "}" + "}"; try { - gitHubController.initializeGitHubSecret(); + gitHubController.initializeGitHubController(); boolean secret = gitHubController.wrongSecret(signature, json_to_test); assertEquals(secret, false); secret = gitHubController.wrongSecret(signature + " ", json_to_test); @@ -164,7 +164,7 @@ public void wrongSecretTest() { @Test @SetEnvironmentVariable(key = "LPVS_GITHUB_SECRET", value = "LPVS") public void testGitHubSingleScan_Success() throws Exception { - Method method = gitHubController.getClass().getDeclaredMethod("initializeGitHubSecret"); + Method method = gitHubController.getClass().getDeclaredMethod("initializeGitHubController"); method.setAccessible(true); method.invoke(gitHubController); LPVSQueue mockScanConfig = new LPVSQueue(); @@ -173,7 +173,25 @@ public void testGitHubSingleScan_Success() throws Exception { when(mocked_queueRepo.save(any())).thenReturn(mockScanConfig); doNothing().when(mocked_instance_queueServ).addFirst(any()); ResponseEntity responseEntity = - gitHubController.gitHubSingleScan("github.com", "org", "repo", 1); + gitHubController.gitHubSingleScan("org", "repo", 1); + + assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); + } + + @Test + @SetEnvironmentVariable(key = "LPVS_GITHUB_SECRET", value = "LPVS") + @SetEnvironmentVariable(key = "LPVS_GITHUB_API_URL", value = "https://api.github.com") + public void testGitHubSingleScan_SuccessSetEnvironment() throws Exception { + Method method = gitHubController.getClass().getDeclaredMethod("initializeGitHubController"); + method.setAccessible(true); + method.invoke(gitHubController); + LPVSQueue mockScanConfig = new LPVSQueue(); + when(mocked_instance_ghServ.getInternalQueueByPullRequest(anyString())) + .thenReturn(mockScanConfig); + when(mocked_queueRepo.save(any())).thenReturn(mockScanConfig); + doNothing().when(mocked_instance_queueServ).addFirst(any()); + ResponseEntity responseEntity = + gitHubController.gitHubSingleScan("org", "repo", 1); assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); } @@ -183,7 +201,7 @@ public void testGitHubSingleScan_Success() throws Exception { public void testGitHubSingleScan_InvalidSecret() throws Exception { when(mocked_instance_ghServ.getInternalQueueByPullRequest(anyString())).thenReturn(null); ResponseEntity responseEntity = - gitHubController.gitHubSingleScan("", "org", "repo", 1); + gitHubController.gitHubSingleScan("org", "repo", 1); assertEquals(HttpStatus.FORBIDDEN, responseEntity.getStatusCode()); } @@ -194,7 +212,7 @@ public void testGitHubSingleScan_ConnectionError() throws Exception { when(mocked_instance_ghServ.getInternalQueueByPullRequest(anyString())) .thenThrow(new RuntimeException("Connection error")); ResponseEntity responseEntity = - gitHubController.gitHubSingleScan("github.com", "org", "repo", 1); + gitHubController.gitHubSingleScan("org", "repo", 1); assertEquals(HttpStatus.FORBIDDEN, responseEntity.getStatusCode()); } From 000ea7ef264b6d7e2d27016aafdf43e9d11b07fb Mon Sep 17 00:00:00 2001 From: Oleg Kopysov Date: Wed, 27 Dec 2023 14:22:28 +0200 Subject: [PATCH 06/12] feat: Add API for single pull request scan Signed-off-by: Oleg Kopysov --- pom.xml | 4 + ...sController.java => GitHubController.java} | 81 +++++++++++++++-- .../com/lpvs/controller/package-info.java | 2 +- .../entity/enums/LPVSPullRequestAction.java | 9 +- .../com/lpvs/service/LPVSDetectService.java | 87 +++---------------- .../com/lpvs/service/LPVSGitHubService.java | 32 +++++++ .../com/lpvs/service/LPVSQueueService.java | 4 +- src/main/java/com/lpvs/util/LPVSFileUtil.java | 11 +++ .../java/com/lpvs/util/LPVSWebhookUtil.java | 29 +++++++ ...lerTest.java => GitHubControllerTest.java} | 20 ++--- .../lpvs/service/LPVSDetectServiceTest.java | 79 +++++------------ .../lpvs/service/LPVSGitHubServiceTest.java | 70 +++++++++++---- 12 files changed, 254 insertions(+), 174 deletions(-) rename src/main/java/com/lpvs/controller/{GitHubWebhooksController.java => GitHubController.java} (67%) rename src/test/java/com/lpvs/controller/{GitHubWebhooksControllerTest.java => GitHubControllerTest.java} (89%) diff --git a/pom.xml b/pom.xml index a0bf91d3..e6a61c49 100644 --- a/pom.xml +++ b/pom.xml @@ -136,6 +136,10 @@ h2 2.2.220 + + org.springframework.boot + spring-boot-starter-validation + diff --git a/src/main/java/com/lpvs/controller/GitHubWebhooksController.java b/src/main/java/com/lpvs/controller/GitHubController.java similarity index 67% rename from src/main/java/com/lpvs/controller/GitHubWebhooksController.java rename to src/main/java/com/lpvs/controller/GitHubController.java index 4c7af690..bf09f2b3 100644 --- a/src/main/java/com/lpvs/controller/GitHubWebhooksController.java +++ b/src/main/java/com/lpvs/controller/GitHubController.java @@ -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; @@ -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; @@ -38,7 +40,7 @@ */ @RestController @Slf4j -public class GitHubWebhooksController { +public class GitHubController { /** * The GitHub secret used for validating webhook payloads. @@ -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. @@ -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, @@ -166,6 +168,69 @@ public ResponseEntity 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 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. * diff --git a/src/main/java/com/lpvs/controller/package-info.java b/src/main/java/com/lpvs/controller/package-info.java index 321d8a2e..214570b9 100644 --- a/src/main/java/com/lpvs/controller/package-info.java +++ b/src/main/java/com/lpvs/controller/package-info.java @@ -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. *

- * - {@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. *

* - {@link com.lpvs.controller.LPVSWebController}: Controls the web interface and API endpoints for LPVS, including user diff --git a/src/main/java/com/lpvs/entity/enums/LPVSPullRequestAction.java b/src/main/java/com/lpvs/entity/enums/LPVSPullRequestAction.java index 2ead8219..d73bbe43 100644 --- a/src/main/java/com/lpvs/entity/enums/LPVSPullRequestAction.java +++ b/src/main/java/com/lpvs/entity/enums/LPVSPullRequestAction.java @@ -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. @@ -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; } diff --git a/src/main/java/com/lpvs/service/LPVSDetectService.java b/src/main/java/com/lpvs/service/LPVSDetectService.java index 98cf9bd6..3edc31cd 100644 --- a/src/main/java/com/lpvs/service/LPVSDetectService.java +++ b/src/main/java/com/lpvs/service/LPVSDetectService.java @@ -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; @@ -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; /** @@ -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. */ @@ -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; } /** @@ -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 scanResult = this.runScan( - webhookConfig, - LPVSDetectService.getPathByPullRequest(webhookConfig)); + webhookConfig, LPVSFileUtil.getPathByPullRequest(webhookConfig)); List> detectedConflicts = licenseService.findConflicts(webhookConfig, scanResult); @@ -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. * diff --git a/src/main/java/com/lpvs/service/LPVSGitHubService.java b/src/main/java/com/lpvs/service/LPVSGitHubService.java index 236d656a..7c2f9687 100644 --- a/src/main/java/com/lpvs/service/LPVSGitHubService.java +++ b/src/main/java/com/lpvs/service/LPVSGitHubService.java @@ -28,6 +28,7 @@ import org.springframework.stereotype.Service; import java.io.IOException; +import java.util.Arrays; import java.util.List; /** @@ -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; + } } diff --git a/src/main/java/com/lpvs/service/LPVSQueueService.java b/src/main/java/com/lpvs/service/LPVSQueueService.java index 5847cfb8..beb0ebb6 100644 --- a/src/main/java/com/lpvs/service/LPVSQueueService.java +++ b/src/main/java/com/lpvs/service/LPVSQueueService.java @@ -214,8 +214,8 @@ public LPVSQueue getLatestScan(List 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); diff --git a/src/main/java/com/lpvs/util/LPVSFileUtil.java b/src/main/java/com/lpvs/util/LPVSFileUtil.java index 64db0ae1..7fe1643c 100644 --- a/src/main/java/com/lpvs/util/LPVSFileUtil.java +++ b/src/main/java/com/lpvs/util/LPVSFileUtil.java @@ -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); + } } diff --git a/src/main/java/com/lpvs/util/LPVSWebhookUtil.java b/src/main/java/com/lpvs/util/LPVSWebhookUtil.java index a63b74c5..0e049dcd 100644 --- a/src/main/java/com/lpvs/util/LPVSWebhookUtil.java +++ b/src/main/java/com/lpvs/util/LPVSWebhookUtil.java @@ -11,6 +11,8 @@ import com.lpvs.entity.LPVSQueue; import com.lpvs.entity.enums.LPVSPullRequestAction; import lombok.extern.slf4j.Slf4j; +import org.kohsuke.github.GHPullRequest; +import org.kohsuke.github.GHRepository; import org.springframework.http.HttpHeaders; import java.util.Arrays; @@ -231,4 +233,31 @@ public static HttpHeaders generateSecurityHeaders() { return headers; } + + /** + * 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. + */ + public 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; + } } diff --git a/src/test/java/com/lpvs/controller/GitHubWebhooksControllerTest.java b/src/test/java/com/lpvs/controller/GitHubControllerTest.java similarity index 89% rename from src/test/java/com/lpvs/controller/GitHubWebhooksControllerTest.java rename to src/test/java/com/lpvs/controller/GitHubControllerTest.java index d8f8ed8d..1b4189a0 100644 --- a/src/test/java/com/lpvs/controller/GitHubWebhooksControllerTest.java +++ b/src/test/java/com/lpvs/controller/GitHubControllerTest.java @@ -23,7 +23,7 @@ import static org.mockito.Mockito.mock; @Slf4j -public class GitHubWebhooksControllerTest { +public class GitHubControllerTest { private static final String SIGNATURE = "X-Hub-Signature-256"; private static final String SUCCESS = "Success"; @@ -34,8 +34,8 @@ public class GitHubWebhooksControllerTest { LPVSQueueService mocked_instance_queueServ = mock(LPVSQueueService.class); LPVSGitHubService mocked_instance_ghServ = mock(LPVSGitHubService.class); LPVSQueueRepository mocked_queueRepo = mock(LPVSQueueRepository.class); - GitHubWebhooksController gitHubWebhooksController = - new GitHubWebhooksController( + GitHubController gitHubController = + new GitHubController( mocked_instance_queueServ, mocked_instance_ghServ, mocked_queueRepo, @@ -46,7 +46,7 @@ public class GitHubWebhooksControllerTest { public void noSignatureTest() { ResponseEntity actual; try { - actual = gitHubWebhooksController.gitHubWebhooks(null, null); + actual = gitHubController.gitHubWebhooks(null, null); } catch (Exception e) { actual = null; } @@ -59,7 +59,7 @@ public void noSignatureTest() { public void noPayloadTest() { ResponseEntity actual; try { - actual = gitHubWebhooksController.gitHubWebhooks(SIGNATURE, null); + actual = gitHubController.gitHubWebhooks(SIGNATURE, null); } catch (Exception e) { actual = null; } @@ -109,7 +109,7 @@ public void okTest() { + "}"; try { - actual = gitHubWebhooksController.gitHubWebhooks(SIGNATURE, json_to_test); + actual = gitHubController.gitHubWebhooks(SIGNATURE, json_to_test); } catch (Exception e) { log.error(e.getMessage()); actual = null; @@ -148,13 +148,13 @@ public void wrongSecretTest() { + "}" + "}"; try { - gitHubWebhooksController.initializeGitHubSecret(); - boolean secret = gitHubWebhooksController.wrongSecret(signature, json_to_test); + gitHubController.initializeGitHubSecret(); + boolean secret = gitHubController.wrongSecret(signature, json_to_test); assertEquals(secret, false); - secret = gitHubWebhooksController.wrongSecret(signature + " ", json_to_test); + secret = gitHubController.wrongSecret(signature + " ", json_to_test); assertEquals(secret, true); } catch (Exception e) { - log.error("GitHubWebhooksControllerTest::wrongSecretTest exception: " + e); + log.error("GitHubControllerTest::wrongSecretTest exception: " + e); fail(); } } diff --git a/src/test/java/com/lpvs/service/LPVSDetectServiceTest.java b/src/test/java/com/lpvs/service/LPVSDetectServiceTest.java index fcfe3b41..907e77ea 100644 --- a/src/test/java/com/lpvs/service/LPVSDetectServiceTest.java +++ b/src/test/java/com/lpvs/service/LPVSDetectServiceTest.java @@ -11,6 +11,7 @@ import com.lpvs.service.scanner.scanoss.LPVSScanossDetectService; import com.lpvs.util.LPVSCommentUtil; +import com.lpvs.util.LPVSFileUtil; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeEach; @@ -24,8 +25,6 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.boot.context.event.ApplicationReadyEvent; -import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationEventPublisher; import java.io.IOException; @@ -39,7 +38,6 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -51,25 +49,14 @@ public class LPVSDetectServiceTest { @Mock private ApplicationEventPublisher mockEventPublisher; - @Mock private LPVSGitHubConnectionService gitHubConnectionService; - - @Mock private GitHub gitHub; - - @Mock private GHRepository ghRepository; - - @Mock private GHPullRequest ghPullRequest; - @Mock private LPVSScanossDetectService scanossDetectService; - @Mock private ApplicationContext applicationContext; - - @Mock private ApplicationReadyEvent applicationReadyEvent; - @InjectMocks private LPVSDetectService lpvsDetectService; @Nested class TestInit { - final LPVSDetectService detectService = new LPVSDetectService("scanoss", null, null, null); + final LPVSDetectService detectService = + new LPVSDetectService("scanoss", null, null, null, null); @Test public void testInit() { @@ -90,6 +77,7 @@ class TestRunScan__Scanoss { LPVSGitHubConnectionService github_mock = mock(LPVSGitHubConnectionService.class); LPVSScanossDetectService scanoss_mock = mock(LPVSScanossDetectService.class); LPVSLicenseService licenseservice_mock = mock(LPVSLicenseService.class); + LPVSGitHubService githubservice_mock = mock(LPVSGitHubService.class); GitHub mockGitHub = mock(GitHub.class); GHCommitPointer mockCommitPointer = mock(GHCommitPointer.class); GHRepository mockRepository = mock(GHRepository.class); @@ -105,7 +93,11 @@ class TestRunScan__Scanoss { void setUp() throws IOException { detectService = new LPVSDetectService( - "scanoss", github_mock, scanoss_mock, licenseservice_mock); + "scanoss", + github_mock, + scanoss_mock, + licenseservice_mock, + githubservice_mock); webhookConfig = new LPVSQueue(); webhookConfig.setId(1L); @@ -127,7 +119,7 @@ void setUp() throws IOException { @Test void testRunOneScanWithNullTriger() throws NoSuchFieldException, IllegalAccessException { lpvsDetectService = - spy(new LPVSDetectService("scanoss", null, scanossDetectService, null)); + spy(new LPVSDetectService("scanoss", null, scanossDetectService, null, null)); setPrivateField(lpvsDetectService, "trigger", null); setPrivateField(lpvsDetectService, "eventPublisher", mockEventPublisher); @@ -143,7 +135,7 @@ void testRunOneScanWithNullTriger() throws NoSuchFieldException, IllegalAccessEx void testRunOneScan_Default() throws NoSuchFieldException, IllegalAccessException { lpvsDetectService = - spy(new LPVSDetectService("scanoss", null, scanossDetectService, null)); + spy(new LPVSDetectService("scanoss", null, scanossDetectService, null, null)); setPrivateField(lpvsDetectService, "trigger", "fake-trigger-value"); setPrivateField(lpvsDetectService, "eventPublisher", mockEventPublisher); @@ -162,7 +154,7 @@ void testRunOneScan_Branch2() List.of(conflict_1, conflict_1); lpvsDetectService = - spy(new LPVSDetectService("scanoss", null, scanossDetectService, null)); + spy(new LPVSDetectService("scanoss", null, scanossDetectService, null, null)); // Mock the necessary GitHub objects for LPVSQueue when(mockGitHub.getRepository(any())).thenReturn(mockRepository); @@ -204,7 +196,7 @@ void testRunOneScan_Branch3() doNothing().when(mockEventPublisher).publishEvent(any()); lpvsDetectService = - spy(new LPVSDetectService("scanoss", null, scanossDetectService, null)); + spy(new LPVSDetectService("scanoss", null, scanossDetectService, null, null)); setPrivateField(detectService, "trigger", "github/owner/repo/branch/123"); setPrivateField(detectService, "scannerType", "scanoss"); @@ -329,7 +321,6 @@ void testRunOneScan_TriggerNotNull_Branch3() throws Exception { when(mockPullRequest.getHead()).thenReturn(mockCommitPointer); when(licenseservice_mock.findConflicts(webhookConfig, null)).thenReturn(expected); when(mockCommitPointer.getRepository()).thenReturn(mockHeadRepository2); - when(mockHeadRepository2.getHtmlUrl()).thenReturn(null); // Set up expected values String expectedPullRequestUrl = "https://example.com/pull/1"; @@ -361,45 +352,12 @@ void testCommentBuilder_ConflictFilePresent() throws Exception { assertNotNull(commentHTML); } - @Test - void testGetInternalQueueByPullRequestWithNull() throws IOException { - LPVSQueue result = lpvsDetectService.getInternalQueueByPullRequest(null); - assertNull(result); - } - - @Test - void testGetInternalQueueByPullRequest() throws IOException { - String pullRequest = "github/owner/repo/branch/123"; - when(gitHubConnectionService.connectToGitHubApi()).thenReturn(gitHub); - when(gitHub.getRepository("owner/repo")).thenReturn(ghRepository); - when(ghRepository.getPullRequest(123)).thenReturn(ghPullRequest); - - LPVSQueue result = lpvsDetectService.getInternalQueueByPullRequest(pullRequest); - - assertNotNull(result); - assertEquals(result.getUserId(), "Single scan run"); - } - - @Test - void testGetInternalQueueByPullRequestError() throws IOException { - String pullRequest = "github/owner/repo/branch/123"; - - when(gitHubConnectionService.connectToGitHubApi()).thenThrow(IOException.class); - - try { - LPVSQueue result = lpvsDetectService.getInternalQueueByPullRequest(pullRequest); - assertNull(result, "Expected result to be null"); - } catch (Exception e) { - fail("Exception not expected to be thrown here"); - } - } - @Test public void testGetPathByPullRequest() { LPVSQueue mockWebhookConfig = mock(LPVSQueue.class); - String result = LPVSDetectService.getPathByPullRequest(mockWebhookConfig); + String result = LPVSFileUtil.getPathByPullRequest(mockWebhookConfig); assertNotNull(result); } @@ -434,6 +392,7 @@ class TestRunScan__ScanossException { LPVSGitHubConnectionService github_mock = mock(LPVSGitHubConnectionService.class); LPVSScanossDetectService scanoss_mock = mock(LPVSScanossDetectService.class); LPVSLicenseService licenseservice_mock = mock(LPVSLicenseService.class); + LPVSGitHubService githubservice_mock = mock(LPVSGitHubService.class); LPVSQueue webhookConfig; final String test_path = "test_path"; @@ -443,7 +402,11 @@ class TestRunScan__ScanossException { void setUp() { detectService = new LPVSDetectService( - "scanoss", github_mock, scanoss_mock, licenseservice_mock); + "scanoss", + github_mock, + scanoss_mock, + licenseservice_mock, + githubservice_mock); webhookConfig = new LPVSQueue(); webhookConfig.setId(1L); @@ -488,7 +451,7 @@ class TestRunScan__NotScanoss { @BeforeEach void setUp() { - detectService = new LPVSDetectService("not_scanoss", null, null, null); + detectService = new LPVSDetectService("not_scanoss", null, null, null, null); } @Test diff --git a/src/test/java/com/lpvs/service/LPVSGitHubServiceTest.java b/src/test/java/com/lpvs/service/LPVSGitHubServiceTest.java index 1f62098b..ddaa6694 100644 --- a/src/test/java/com/lpvs/service/LPVSGitHubServiceTest.java +++ b/src/test/java/com/lpvs/service/LPVSGitHubServiceTest.java @@ -24,11 +24,11 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.junitpioneer.jupiter.SetEnvironmentVariable; import org.kohsuke.github.*; -import org.mockito.MockedStatic; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; import java.io.IOException; @@ -48,6 +48,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; @Slf4j +@ExtendWith(MockitoExtension.class) public class LPVSGitHubServiceTest { /** @@ -60,6 +61,16 @@ public class LPVSGitHubServiceTest { */ private LPVSExitHandler exitHandler; + @Mock private GitHub gitHub; + + @Mock private GHRepository ghRepository; + + @Mock private GHPullRequest ghPullRequest; + + @Mock private LPVSGitHubConnectionService gitHubConnectionService; + + @InjectMocks private LPVSGitHubService gitHubService; + static class GHPullRequestOurMock extends GHPullRequest { private final URL mockedGetUrl; private final String mockedGetTitle; @@ -4316,25 +4327,10 @@ public void testCheckNotEmpty() String GH_LOGIN = ""; String GH_AUTH_TOKEN = "non-empty"; String GH_API_URL = ""; - LPVSPullRequestRepository mocked_pullRequestRepository = - mock(LPVSPullRequestRepository.class); - LPVSDetectedLicenseRepository mocked_lpvsDetectedLicenseRepository = - mock(LPVSDetectedLicenseRepository.class); - LPVSLicenseRepository mocked_lpvsLicenseRepository = mock(LPVSLicenseRepository.class); - LPVSLicenseConflictRepository mocked_lpvsLicenseConflictRepository = - mock(LPVSLicenseConflictRepository.class); LPVSExitHandler exitHandler = mock(LPVSExitHandler.class); LPVSGitHubConnectionService lpvsGitHubConnectionService = new LPVSGitHubConnectionService( GH_LOGIN, GH_AUTH_TOKEN, GH_API_URL, exitHandler); - - final LPVSGitHubService gh_service = - new LPVSGitHubService( - mocked_pullRequestRepository, - mocked_lpvsDetectedLicenseRepository, - mocked_lpvsLicenseRepository, - mocked_lpvsLicenseConflictRepository, - lpvsGitHubConnectionService); Method method = lpvsGitHubConnectionService.getClass().getDeclaredMethod("checks"); method.setAccessible(true); method.invoke(lpvsGitHubConnectionService); @@ -4342,4 +4338,42 @@ public void testCheckNotEmpty() verify(exitHandler, never()).exit(anyInt()); } } + + @Nested + class getInternalQueueByPullRequests { + + @Test + void testGetInternalQueueByPullRequestWithNull() { + LPVSQueue result = gitHubService.getInternalQueueByPullRequest(null); + assertNull(result); + } + + @Test + void testGetInternalQueueByPullRequest() throws IOException { + String pullRequest = "github/owner/repo/branch/123"; + + when(gitHubConnectionService.connectToGitHubApi()).thenReturn(gitHub); + when(gitHub.getRepository("owner/repo")).thenReturn(ghRepository); + when(ghRepository.getPullRequest(123)).thenReturn(ghPullRequest); + + LPVSQueue result = gitHubService.getInternalQueueByPullRequest(pullRequest); + + assertNotNull(result); + assertEquals(result.getUserId(), "Single scan run"); + } + + @Test + void testGetInternalQueueByPullRequestError() throws IOException { + String pullRequest = "github/owner/repo/branch/123"; + + when(gitHubConnectionService.connectToGitHubApi()).thenThrow(IOException.class); + + try { + LPVSQueue result = gitHubService.getInternalQueueByPullRequest(pullRequest); + assertNull(result, "Expected result to be null"); + } catch (Exception e) { + fail("Exception not expected to be thrown here"); + } + } + } } From fedb3271a1af9c5b560a8c8dbeb1af5d9d15fe61 Mon Sep 17 00:00:00 2001 From: Oleg Kopysov Date: Thu, 28 Dec 2023 14:30:58 +0200 Subject: [PATCH 07/12] docs: Add new endpoint description in YAML file Signed-off-by: Oleg Kopysov --- README.md | 3 ++ doc/lpvs-api.yaml | 47 ++++++++++++++++++- .../com/lpvs/controller/GitHubController.java | 9 ++-- 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index be441111..99b00bc2 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,9 @@ To enable _LPVS_ license scanning for your project, you need to set up GitHub We Configuration from your project side is now complete! +Alternatively, you can use the Pull Request Single Scan API to analyze the code of a specific pull request. +Please refer to the [API Documentation](doc/lpvs-api.yaml) for more information. + --- ### 2. Using pre-built LPVS Docker images diff --git a/doc/lpvs-api.yaml b/doc/lpvs-api.yaml index e571b2e9..e1a31a57 100644 --- a/doc/lpvs-api.yaml +++ b/doc/lpvs-api.yaml @@ -1,7 +1,7 @@ openapi: 3.0.0 info: title: LPVS API - version: v2-20231124 + version: v2-20231228 description: >- License Pre-Validation Service (LPVS) is a tool designed to proactively manage license-related risks in Open Source code. It conducts in-depth analysis of your @@ -48,6 +48,51 @@ paths: schema: $ref: '#/components/schemas/WebhookResponseForbidden' + /scan/{gitHubDomain}/{gitHubOrg}/{gitHubRepo}/{prNumber}: + post: + tags: + - GitHub Pull Request Single Scan API + summary: GitHub Pull Request Single Scan + description: Endpoint for performing a single scan operation based on GitHub organization, repository, and pull request number. + parameters: + - in: path + name: gitHubDomain + required: true + schema: + type: string + description: GitHub domain name + - in: path + name: gitHubOrg + required: true + schema: + type: string + description: GitHub organization name + - in: path + name: gitHubRepo + required: true + schema: + type: string + description: GitHub repository name + - in: path + name: prNumber + required: true + schema: + type: integer + description: Pull request number + responses: + '200': + description: 200 OK + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookResponseOK' + '403': + description: 403 Forbidden + content: + application/json: + schema: + $ref: '#/components/schemas/WebhookResponseForbidden' + /api/v1/web/user/login: get: tags: diff --git a/src/main/java/com/lpvs/controller/GitHubController.java b/src/main/java/com/lpvs/controller/GitHubController.java index bf09f2b3..ca0dd5d5 100644 --- a/src/main/java/com/lpvs/controller/GitHubController.java +++ b/src/main/java/com/lpvs/controller/GitHubController.java @@ -175,6 +175,7 @@ public ResponseEntity gitHubWebhooks( * and pull request number provided in the path variables. The method validates * the input parameters and performs necessary security checks. * + * @param gitHubDomain The GitHub domain name. * @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. @@ -183,10 +184,10 @@ public ResponseEntity gitHubWebhooks( * If there are validation errors or security issues, returns HTTP 403 FORBIDDEN. */ @RequestMapping( - value = "/scan/{gitHubUrl}/{gitHubOrg}/{gitHubRepo}/{prNumber}", + value = "/scan/{gitHubDomain}/{gitHubOrg}/{gitHubRepo}/{prNumber}", method = RequestMethod.POST) public ResponseEntity gitHubSingleScan( - @PathVariable("gitHubUrl") @NotEmpty @Valid String gitHubUrl, + @PathVariable("gitHubDomain") @NotEmpty @Valid String gitHubDomain, @PathVariable("gitHubOrg") @NotEmpty @Valid String gitHubOrg, @PathVariable("gitHubRepo") @NotEmpty @Valid String gitHubRepo, @PathVariable("prNumber") @Min(1) @Valid Integer prNumber) @@ -201,12 +202,12 @@ public ResponseEntity gitHubSingleScan( } // Validate and sanitize user inputs to prevent XSS attacks - gitHubUrl = HtmlUtils.htmlEscape(gitHubUrl); + gitHubDomain = HtmlUtils.htmlEscape(gitHubDomain); gitHubOrg = HtmlUtils.htmlEscape(gitHubOrg); gitHubRepo = HtmlUtils.htmlEscape(gitHubRepo); String prUrl = - "https://" + gitHubUrl + "/" + gitHubOrg + "/" + gitHubRepo + "/pull/" + prNumber; + "https://" + gitHubDomain + "/" + gitHubOrg + "/" + gitHubRepo + "/pull/" + prNumber; LPVSQueue scanConfig = gitHubService.getInternalQueueByPullRequest(HtmlUtils.htmlEscape(prUrl)); if (scanConfig == null) { From 289899fad42d13b7966ba38a49873891ac035ed4 Mon Sep 17 00:00:00 2001 From: Oleg Kopysov Date: Thu, 28 Dec 2023 15:09:27 +0200 Subject: [PATCH 08/12] test: Cover new endpoint by unit tests Signed-off-by: Oleg Kopysov --- doc/lpvs-api.yaml | 4 ++ .../com/lpvs/controller/GitHubController.java | 9 +++- .../lpvs/controller/GitHubControllerTest.java | 44 ++++++++++++++++++- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/doc/lpvs-api.yaml b/doc/lpvs-api.yaml index e1a31a57..c7266df7 100644 --- a/doc/lpvs-api.yaml +++ b/doc/lpvs-api.yaml @@ -61,24 +61,28 @@ paths: schema: type: string description: GitHub domain name + example: 'github.com' - in: path name: gitHubOrg required: true schema: type: string description: GitHub organization name + example: 'Samsung' - in: path name: gitHubRepo required: true schema: type: string description: GitHub repository name + example: 'LPVS' - in: path name: prNumber required: true schema: type: integer description: Pull request number + example: 100 responses: '200': description: 200 OK diff --git a/src/main/java/com/lpvs/controller/GitHubController.java b/src/main/java/com/lpvs/controller/GitHubController.java index ca0dd5d5..57f6889a 100644 --- a/src/main/java/com/lpvs/controller/GitHubController.java +++ b/src/main/java/com/lpvs/controller/GitHubController.java @@ -207,7 +207,14 @@ public ResponseEntity gitHubSingleScan( gitHubRepo = HtmlUtils.htmlEscape(gitHubRepo); String prUrl = - "https://" + gitHubDomain + "/" + gitHubOrg + "/" + gitHubRepo + "/pull/" + prNumber; + "https://" + + gitHubDomain + + "/" + + gitHubOrg + + "/" + + gitHubRepo + + "/pull/" + + prNumber; LPVSQueue scanConfig = gitHubService.getInternalQueueByPullRequest(HtmlUtils.htmlEscape(prUrl)); if (scanConfig == null) { diff --git a/src/test/java/com/lpvs/controller/GitHubControllerTest.java b/src/test/java/com/lpvs/controller/GitHubControllerTest.java index 1b4189a0..c1d95438 100644 --- a/src/test/java/com/lpvs/controller/GitHubControllerTest.java +++ b/src/test/java/com/lpvs/controller/GitHubControllerTest.java @@ -6,6 +6,7 @@ */ package com.lpvs.controller; +import com.lpvs.entity.LPVSQueue; import com.lpvs.entity.LPVSResponseWrapper; import com.lpvs.repository.LPVSQueueRepository; import com.lpvs.service.LPVSGitHubService; @@ -18,9 +19,11 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import java.lang.reflect.Method; + import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.*; @Slf4j public class GitHubControllerTest { @@ -71,7 +74,6 @@ public void noPayloadTest() { @Test public void okTest() { ResponseEntity actual; - LPVSQueueRepository queueRepository; String json_to_test = "{" @@ -158,4 +160,42 @@ public void wrongSecretTest() { fail(); } } + + @Test + @SetEnvironmentVariable(key = "LPVS_GITHUB_SECRET", value = "LPVS") + public void testGitHubSingleScan_Success() throws Exception { + Method method = gitHubController.getClass().getDeclaredMethod("initializeGitHubSecret"); + method.setAccessible(true); + method.invoke(gitHubController); + LPVSQueue mockScanConfig = new LPVSQueue(); + when(mocked_instance_ghServ.getInternalQueueByPullRequest(anyString())) + .thenReturn(mockScanConfig); + when(mocked_queueRepo.save(any())).thenReturn(mockScanConfig); + doNothing().when(mocked_instance_queueServ).addFirst(any()); + ResponseEntity responseEntity = + gitHubController.gitHubSingleScan("github.com", "org", "repo", 1); + + assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); + } + + @Test + @SetEnvironmentVariable(key = "LPVS_GITHUB_SECRET", value = "LPVS") + public void testGitHubSingleScan_InvalidSecret() throws Exception { + when(mocked_instance_ghServ.getInternalQueueByPullRequest(anyString())).thenReturn(null); + ResponseEntity responseEntity = + gitHubController.gitHubSingleScan("", "org", "repo", 1); + + assertEquals(HttpStatus.FORBIDDEN, responseEntity.getStatusCode()); + } + + @Test + @SetEnvironmentVariable(key = "LPVS_GITHUB_SECRET", value = "LPVS") + public void testGitHubSingleScan_ConnectionError() throws Exception { + when(mocked_instance_ghServ.getInternalQueueByPullRequest(anyString())) + .thenThrow(new RuntimeException("Connection error")); + ResponseEntity responseEntity = + gitHubController.gitHubSingleScan("github.com", "org", "repo", 1); + + assertEquals(HttpStatus.FORBIDDEN, responseEntity.getStatusCode()); + } } From 61091480f8f3270f62ef536ec8f94aea6374efe1 Mon Sep 17 00:00:00 2001 From: Oleg Kopysov Date: Thu, 28 Dec 2023 15:32:45 +0200 Subject: [PATCH 09/12] test: Update unit tests for LPVSPullRequestAction Signed-off-by: Oleg Kopysov --- .../com/lpvs/entity/enums/LPVSPullRequestActionTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/test/java/com/lpvs/entity/enums/LPVSPullRequestActionTest.java b/src/test/java/com/lpvs/entity/enums/LPVSPullRequestActionTest.java index 8dae36b5..d2973d73 100644 --- a/src/test/java/com/lpvs/entity/enums/LPVSPullRequestActionTest.java +++ b/src/test/java/com/lpvs/entity/enums/LPVSPullRequestActionTest.java @@ -20,6 +20,9 @@ public void testConvertFrom() { assertEquals( LPVSPullRequestAction.convertFrom("synchronize"), LPVSPullRequestAction.UPDATE); assertEquals(LPVSPullRequestAction.convertFrom("rescan"), LPVSPullRequestAction.RESCAN); + assertEquals( + LPVSPullRequestAction.convertFrom("single-scan"), + LPVSPullRequestAction.SINGLE_SCAN); assertNotEquals( LPVSPullRequestAction.convertFrom("random_name"), LPVSPullRequestAction.OPEN); @@ -31,6 +34,9 @@ public void testConvertFrom() { LPVSPullRequestAction.convertFrom("random_name"), LPVSPullRequestAction.UPDATE); assertNotEquals( LPVSPullRequestAction.convertFrom("random_name"), LPVSPullRequestAction.RESCAN); + assertNotEquals( + LPVSPullRequestAction.convertFrom("random_name"), + LPVSPullRequestAction.SINGLE_SCAN); assertNull(LPVSPullRequestAction.convertFrom("random_name")); } @@ -42,5 +48,6 @@ public void testGetPullRequestAction() { assertEquals(LPVSPullRequestAction.CLOSE.getPullRequestAction(), "closed"); assertEquals(LPVSPullRequestAction.UPDATE.getPullRequestAction(), "synchronize"); assertEquals(LPVSPullRequestAction.RESCAN.getPullRequestAction(), "rescan"); + assertEquals(LPVSPullRequestAction.SINGLE_SCAN.getPullRequestAction(), "single-scan"); } } From 1d469cc0c03cb3c704e05bb23b816c6fa9caa8c2 Mon Sep 17 00:00:00 2001 From: Oleg Kopysov Date: Tue, 2 Jan 2024 13:19:02 +0200 Subject: [PATCH 10/12] fix: Update endpoint based on the code review comments Signed-off-by: Oleg Kopysov --- doc/lpvs-api.yaml | 9 +---- .../com/lpvs/controller/GitHubController.java | 40 ++++++++++++++++--- .../lpvs/controller/GitHubControllerTest.java | 28 ++++++++++--- 3 files changed, 58 insertions(+), 19 deletions(-) diff --git a/doc/lpvs-api.yaml b/doc/lpvs-api.yaml index c7266df7..34b67c8e 100644 --- a/doc/lpvs-api.yaml +++ b/doc/lpvs-api.yaml @@ -48,20 +48,13 @@ paths: schema: $ref: '#/components/schemas/WebhookResponseForbidden' - /scan/{gitHubDomain}/{gitHubOrg}/{gitHubRepo}/{prNumber}: + /scan/{gitHubOrg}/{gitHubRepo}/{prNumber}: post: tags: - GitHub Pull Request Single Scan API summary: GitHub Pull Request Single Scan description: Endpoint for performing a single scan operation based on GitHub organization, repository, and pull request number. parameters: - - in: path - name: gitHubDomain - required: true - schema: - type: string - description: GitHub domain name - example: 'github.com' - in: path name: gitHubOrg required: true diff --git a/src/main/java/com/lpvs/controller/GitHubController.java b/src/main/java/com/lpvs/controller/GitHubController.java index 57f6889a..5491815f 100644 --- a/src/main/java/com/lpvs/controller/GitHubController.java +++ b/src/main/java/com/lpvs/controller/GitHubController.java @@ -35,7 +35,7 @@ import javax.crypto.spec.SecretKeySpec; /** - * Controller class for handling GitHub webhook events. + * Controller class for handling GitHub webhook events and single scan requests. * This class is responsible for processing GitHub webhook payloads and triggering relevant actions. */ @RestController @@ -48,12 +48,18 @@ public class GitHubController { */ private String GITHUB_SECRET; + /** + * Name of the GitHub API URL. + * It is set from the LPVS_GITHUB_API_URL environment variable or the application property. + */ + private String GITHUB_API_URL; + /** * Initializes the GitHub secret from the LPVS_GITHUB_SECRET environment variable or the application property. * Exits the application if the secret is not set. */ @PostConstruct - public void initializeGitHubSecret() { + public void initializeGitHubController() { this.GITHUB_SECRET = Optional.ofNullable(this.GITHUB_SECRET) .filter(s -> !s.isEmpty()) @@ -64,6 +70,17 @@ public void initializeGitHubSecret() { log.error("LPVS_GITHUB_SECRET (github.secret) is not set."); exitHandler.exit(-1); } + + this.GITHUB_API_URL = + Optional.ofNullable(this.GITHUB_API_URL) + .filter(s -> !s.isEmpty()) + .orElse( + Optional.ofNullable(System.getenv("LPVS_GITHUB_API_URL")) + .orElse("")); + if (this.GITHUB_API_URL.isEmpty()) { + log.info( + "LPVS_GITHUB_API_URL (github.api.url) is not set. Default domain \"github.com\" will be used."); + } } /** @@ -175,7 +192,6 @@ public ResponseEntity gitHubWebhooks( * and pull request number provided in the path variables. The method validates * the input parameters and performs necessary security checks. * - * @param gitHubDomain The GitHub domain name. * @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. @@ -184,10 +200,9 @@ public ResponseEntity gitHubWebhooks( * If there are validation errors or security issues, returns HTTP 403 FORBIDDEN. */ @RequestMapping( - value = "/scan/{gitHubDomain}/{gitHubOrg}/{gitHubRepo}/{prNumber}", + value = "/scan/{gitHubOrg}/{gitHubRepo}/{prNumber}", method = RequestMethod.POST) public ResponseEntity gitHubSingleScan( - @PathVariable("gitHubDomain") @NotEmpty @Valid String gitHubDomain, @PathVariable("gitHubOrg") @NotEmpty @Valid String gitHubOrg, @PathVariable("gitHubRepo") @NotEmpty @Valid String gitHubRepo, @PathVariable("prNumber") @Min(1) @Valid Integer prNumber) @@ -202,10 +217,23 @@ public ResponseEntity gitHubSingleScan( } // Validate and sanitize user inputs to prevent XSS attacks - gitHubDomain = HtmlUtils.htmlEscape(gitHubDomain); gitHubOrg = HtmlUtils.htmlEscape(gitHubOrg); gitHubRepo = HtmlUtils.htmlEscape(gitHubRepo); + String gitHubDomain; + if (!GITHUB_API_URL.isEmpty()) { + gitHubDomain = + GITHUB_API_URL + .trim() + .substring( + GITHUB_API_URL.indexOf("https://api.") + + "https://api.".length()) + .replaceAll("/", ""); + } else { + gitHubDomain = "github.com"; + } + log.debug("GitHub domain name is \"" + GITHUB_API_URL + "\""); + String prUrl = "https://" + gitHubDomain diff --git a/src/test/java/com/lpvs/controller/GitHubControllerTest.java b/src/test/java/com/lpvs/controller/GitHubControllerTest.java index c1d95438..828b1f1e 100644 --- a/src/test/java/com/lpvs/controller/GitHubControllerTest.java +++ b/src/test/java/com/lpvs/controller/GitHubControllerTest.java @@ -150,7 +150,7 @@ public void wrongSecretTest() { + "}" + "}"; try { - gitHubController.initializeGitHubSecret(); + gitHubController.initializeGitHubController(); boolean secret = gitHubController.wrongSecret(signature, json_to_test); assertEquals(secret, false); secret = gitHubController.wrongSecret(signature + " ", json_to_test); @@ -164,7 +164,7 @@ public void wrongSecretTest() { @Test @SetEnvironmentVariable(key = "LPVS_GITHUB_SECRET", value = "LPVS") public void testGitHubSingleScan_Success() throws Exception { - Method method = gitHubController.getClass().getDeclaredMethod("initializeGitHubSecret"); + Method method = gitHubController.getClass().getDeclaredMethod("initializeGitHubController"); method.setAccessible(true); method.invoke(gitHubController); LPVSQueue mockScanConfig = new LPVSQueue(); @@ -173,7 +173,25 @@ public void testGitHubSingleScan_Success() throws Exception { when(mocked_queueRepo.save(any())).thenReturn(mockScanConfig); doNothing().when(mocked_instance_queueServ).addFirst(any()); ResponseEntity responseEntity = - gitHubController.gitHubSingleScan("github.com", "org", "repo", 1); + gitHubController.gitHubSingleScan("org", "repo", 1); + + assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); + } + + @Test + @SetEnvironmentVariable(key = "LPVS_GITHUB_SECRET", value = "LPVS") + @SetEnvironmentVariable(key = "LPVS_GITHUB_API_URL", value = "https://api.github.com") + public void testGitHubSingleScan_SuccessSetEnvironment() throws Exception { + Method method = gitHubController.getClass().getDeclaredMethod("initializeGitHubController"); + method.setAccessible(true); + method.invoke(gitHubController); + LPVSQueue mockScanConfig = new LPVSQueue(); + when(mocked_instance_ghServ.getInternalQueueByPullRequest(anyString())) + .thenReturn(mockScanConfig); + when(mocked_queueRepo.save(any())).thenReturn(mockScanConfig); + doNothing().when(mocked_instance_queueServ).addFirst(any()); + ResponseEntity responseEntity = + gitHubController.gitHubSingleScan("org", "repo", 1); assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); } @@ -183,7 +201,7 @@ public void testGitHubSingleScan_Success() throws Exception { public void testGitHubSingleScan_InvalidSecret() throws Exception { when(mocked_instance_ghServ.getInternalQueueByPullRequest(anyString())).thenReturn(null); ResponseEntity responseEntity = - gitHubController.gitHubSingleScan("", "org", "repo", 1); + gitHubController.gitHubSingleScan("org", "repo", 1); assertEquals(HttpStatus.FORBIDDEN, responseEntity.getStatusCode()); } @@ -194,7 +212,7 @@ public void testGitHubSingleScan_ConnectionError() throws Exception { when(mocked_instance_ghServ.getInternalQueueByPullRequest(anyString())) .thenThrow(new RuntimeException("Connection error")); ResponseEntity responseEntity = - gitHubController.gitHubSingleScan("github.com", "org", "repo", 1); + gitHubController.gitHubSingleScan("org", "repo", 1); assertEquals(HttpStatus.FORBIDDEN, responseEntity.getStatusCode()); } From 990d4c6551f165197b02cd909e17d0fc0517122f Mon Sep 17 00:00:00 2001 From: Oleg Kopysov Date: Thu, 4 Jan 2024 11:19:53 +0200 Subject: [PATCH 11/12] fix: Fix build after incorrect rebase Signed-off-by: Oleg Kopysov --- src/test/java/com/lpvs/service/LPVSDetectServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/lpvs/service/LPVSDetectServiceTest.java b/src/test/java/com/lpvs/service/LPVSDetectServiceTest.java index 8eef2267..75d07814 100644 --- a/src/test/java/com/lpvs/service/LPVSDetectServiceTest.java +++ b/src/test/java/com/lpvs/service/LPVSDetectServiceTest.java @@ -364,7 +364,7 @@ public void testGetPathByPullRequest() { when(mockWebhookConfig.getPullRequestUrl()) .thenReturn("https://github.com/Samsung/LPVS/pull/1"); - String result = LPVSDetectService.getPathByPullRequest(mockWebhookConfig); + String result = LPVSFileUtil.getPathByPullRequest(mockWebhookConfig); assertNotNull(result); } From a660f711ef38239abaf7d4cf27b0d89b27dc1b20 Mon Sep 17 00:00:00 2001 From: Oleg Kopysov Date: Thu, 4 Jan 2024 17:01:51 +0200 Subject: [PATCH 12/12] fix: Update code based on the review comments Signed-off-by: Oleg Kopysov --- .../com/lpvs/controller/GitHubController.java | 60 ++++++------------- .../lpvs/controller/GitHubControllerTest.java | 28 ++++----- 2 files changed, 30 insertions(+), 58 deletions(-) diff --git a/src/main/java/com/lpvs/controller/GitHubController.java b/src/main/java/com/lpvs/controller/GitHubController.java index 5491815f..1007006f 100644 --- a/src/main/java/com/lpvs/controller/GitHubController.java +++ b/src/main/java/com/lpvs/controller/GitHubController.java @@ -9,6 +9,7 @@ import com.lpvs.entity.LPVSQueue; import com.lpvs.entity.enums.LPVSPullRequestAction; import com.lpvs.repository.LPVSQueueRepository; +import com.lpvs.service.LPVSGitHubConnectionService; import com.lpvs.service.LPVSGitHubService; import com.lpvs.service.LPVSQueueService; import com.lpvs.util.LPVSExitHandler; @@ -20,6 +21,9 @@ import javax.validation.constraints.Min; import javax.validation.constraints.NotEmpty; import org.apache.commons.codec.binary.Hex; +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.http.HttpStatus; @@ -28,6 +32,7 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.util.HtmlUtils; +import java.io.IOException; import java.util.Date; import java.util.Optional; import javax.annotation.PostConstruct; @@ -48,12 +53,6 @@ public class GitHubController { */ private String GITHUB_SECRET; - /** - * Name of the GitHub API URL. - * It is set from the LPVS_GITHUB_API_URL environment variable or the application property. - */ - private String GITHUB_API_URL; - /** * Initializes the GitHub secret from the LPVS_GITHUB_SECRET environment variable or the application property. * Exits the application if the secret is not set. @@ -70,17 +69,6 @@ public void initializeGitHubController() { log.error("LPVS_GITHUB_SECRET (github.secret) is not set."); exitHandler.exit(-1); } - - this.GITHUB_API_URL = - Optional.ofNullable(this.GITHUB_API_URL) - .filter(s -> !s.isEmpty()) - .orElse( - Optional.ofNullable(System.getenv("LPVS_GITHUB_API_URL")) - .orElse("")); - if (this.GITHUB_API_URL.isEmpty()) { - log.info( - "LPVS_GITHUB_API_URL (github.api.url) is not set. Default domain \"github.com\" will be used."); - } } /** @@ -98,6 +86,11 @@ public void initializeGitHubController() { */ private LPVSGitHubService gitHubService; + /** + * Service for establishing and managing connections to the GitHub API. + */ + private LPVSGitHubConnectionService gitHubConnectionService; + /** * LPVSExitHandler for handling application exit scenarios. */ @@ -114,6 +107,7 @@ public void initializeGitHubController() { * * @param queueService LPVSQueueService for handling user-related business logic. * @param gitHubService LPVSGitHubService for handling GitHub-related actions. + * @param gitHubConnectionService Service for establishing and managing connections to the GitHub API. * @param queueRepository LPVSQueueRepository for accessing and managing LPVSQueue entities. * @param GITHUB_SECRET The GitHub secret used for validating webhook payloads. * @param exitHandler LPVSExitHandler for handling application exit scenarios. @@ -121,11 +115,13 @@ public void initializeGitHubController() { public GitHubController( LPVSQueueService queueService, LPVSGitHubService gitHubService, + LPVSGitHubConnectionService gitHubConnectionService, LPVSQueueRepository queueRepository, @Value("${github.secret:}") String GITHUB_SECRET, LPVSExitHandler exitHandler) { this.queueService = queueService; this.gitHubService = gitHubService; + this.gitHubConnectionService = gitHubConnectionService; this.queueRepository = queueRepository; this.GITHUB_SECRET = GITHUB_SECRET; this.exitHandler = exitHandler; @@ -206,7 +202,7 @@ public ResponseEntity gitHubSingleScan( @PathVariable("gitHubOrg") @NotEmpty @Valid String gitHubOrg, @PathVariable("gitHubRepo") @NotEmpty @Valid String gitHubRepo, @PathVariable("prNumber") @Min(1) @Valid Integer prNumber) - throws InterruptedException { + throws InterruptedException, IOException { log.debug("New GitHub single scan request received"); if (GITHUB_SECRET.trim().isEmpty()) { @@ -220,31 +216,11 @@ public ResponseEntity gitHubSingleScan( gitHubOrg = HtmlUtils.htmlEscape(gitHubOrg); gitHubRepo = HtmlUtils.htmlEscape(gitHubRepo); - String gitHubDomain; - if (!GITHUB_API_URL.isEmpty()) { - gitHubDomain = - GITHUB_API_URL - .trim() - .substring( - GITHUB_API_URL.indexOf("https://api.") - + "https://api.".length()) - .replaceAll("/", ""); - } else { - gitHubDomain = "github.com"; - } - log.debug("GitHub domain name is \"" + GITHUB_API_URL + "\""); + GitHub gitHub = gitHubConnectionService.connectToGitHubApi(); + GHRepository repository = gitHub.getRepository(gitHubOrg + "/" + gitHubRepo); + GHPullRequest pullRequest = repository.getPullRequest(prNumber); + LPVSQueue scanConfig = LPVSWebhookUtil.getGitHubWebhookConfig(repository, pullRequest); - String prUrl = - "https://" - + gitHubDomain - + "/" - + 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) diff --git a/src/test/java/com/lpvs/controller/GitHubControllerTest.java b/src/test/java/com/lpvs/controller/GitHubControllerTest.java index 828b1f1e..068a918e 100644 --- a/src/test/java/com/lpvs/controller/GitHubControllerTest.java +++ b/src/test/java/com/lpvs/controller/GitHubControllerTest.java @@ -9,6 +9,7 @@ import com.lpvs.entity.LPVSQueue; import com.lpvs.entity.LPVSResponseWrapper; import com.lpvs.repository.LPVSQueueRepository; +import com.lpvs.service.LPVSGitHubConnectionService; import com.lpvs.service.LPVSGitHubService; import com.lpvs.service.LPVSQueueService; @@ -16,6 +17,9 @@ import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.junitpioneer.jupiter.SetEnvironmentVariable; +import org.kohsuke.github.GHPullRequest; +import org.kohsuke.github.GHRepository; +import org.kohsuke.github.GitHub; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -34,13 +38,18 @@ public class GitHubControllerTest { private LPVSExitHandler exitHandler; + GitHub gitHub = mock(GitHub.class); + GHRepository ghRepository = mock(GHRepository.class); + GHPullRequest ghPullRequest = mock(GHPullRequest.class); LPVSQueueService mocked_instance_queueServ = mock(LPVSQueueService.class); LPVSGitHubService mocked_instance_ghServ = mock(LPVSGitHubService.class); LPVSQueueRepository mocked_queueRepo = mock(LPVSQueueRepository.class); + LPVSGitHubConnectionService mocked_ghConnServ = mock(LPVSGitHubConnectionService.class); GitHubController gitHubController = new GitHubController( mocked_instance_queueServ, mocked_instance_ghServ, + mocked_ghConnServ, mocked_queueRepo, "", exitHandler); @@ -172,24 +181,11 @@ public void testGitHubSingleScan_Success() throws Exception { .thenReturn(mockScanConfig); when(mocked_queueRepo.save(any())).thenReturn(mockScanConfig); doNothing().when(mocked_instance_queueServ).addFirst(any()); - ResponseEntity responseEntity = - gitHubController.gitHubSingleScan("org", "repo", 1); - assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); - } + when(mocked_ghConnServ.connectToGitHubApi()).thenReturn(gitHub); + when(gitHub.getRepository("org/repo")).thenReturn(ghRepository); + when(ghRepository.getPullRequest(1)).thenReturn(ghPullRequest); - @Test - @SetEnvironmentVariable(key = "LPVS_GITHUB_SECRET", value = "LPVS") - @SetEnvironmentVariable(key = "LPVS_GITHUB_API_URL", value = "https://api.github.com") - public void testGitHubSingleScan_SuccessSetEnvironment() throws Exception { - Method method = gitHubController.getClass().getDeclaredMethod("initializeGitHubController"); - method.setAccessible(true); - method.invoke(gitHubController); - LPVSQueue mockScanConfig = new LPVSQueue(); - when(mocked_instance_ghServ.getInternalQueueByPullRequest(anyString())) - .thenReturn(mockScanConfig); - when(mocked_queueRepo.save(any())).thenReturn(mockScanConfig); - doNothing().when(mocked_instance_queueServ).addFirst(any()); ResponseEntity responseEntity = gitHubController.gitHubSingleScan("org", "repo", 1);