Skip to content

Commit

Permalink
feat: Add API for single pull request scan (#370)
Browse files Browse the repository at this point in the history
* feat: Add API for single pull request scan

Signed-off-by: Oleg Kopysov <o.kopysov@samsung.com>

* docs: Add new endpoint description in YAML file

Signed-off-by: Oleg Kopysov <o.kopysov@samsung.com>

* test: Cover new endpoint by unit tests

Signed-off-by: Oleg Kopysov <o.kopysov@samsung.com>

* test: Update unit tests for LPVSPullRequestAction

Signed-off-by: Oleg Kopysov <o.kopysov@samsung.com>

* fix: Update endpoint based on the code review comments

Signed-off-by: Oleg Kopysov <o.kopysov@samsung.com>

* feat: Add API for single pull request scan

Signed-off-by: Oleg Kopysov <o.kopysov@samsung.com>

* docs: Add new endpoint description in YAML file

Signed-off-by: Oleg Kopysov <o.kopysov@samsung.com>

* test: Cover new endpoint by unit tests

Signed-off-by: Oleg Kopysov <o.kopysov@samsung.com>

* test: Update unit tests for LPVSPullRequestAction

Signed-off-by: Oleg Kopysov <o.kopysov@samsung.com>

* fix: Update endpoint based on the code review comments

Signed-off-by: Oleg Kopysov <o.kopysov@samsung.com>

* fix: Fix build after incorrect rebase

Signed-off-by: Oleg Kopysov <o.kopysov@samsung.com>

* fix: Update code based on the review comments

Signed-off-by: Oleg Kopysov <o.kopysov@samsung.com>

---------

Signed-off-by: Oleg Kopysov <o.kopysov@samsung.com>
  • Loading branch information
o-kopysov authored Jan 5, 2024
1 parent f7ac60c commit 28ef2d3
Show file tree
Hide file tree
Showing 15 changed files with 377 additions and 179 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
44 changes: 43 additions & 1 deletion doc/lpvs-api.yaml
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -48,6 +48,48 @@ paths:
schema:
$ref: '#/components/schemas/WebhookResponseForbidden'

/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: 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
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:
Expand Down
4 changes: 4 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@
<artifactId>h2</artifactId>
<version>2.2.220</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

</dependencies>

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

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;
import com.lpvs.util.LPVSWebhookUtil;
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.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;
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.io.IOException;
import java.util.Date;
import java.util.Optional;
import javax.annotation.PostConstruct;
import javax.crypto.Mac;
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
@Slf4j
public class GitHubWebhooksController {
public class GitHubController {

/**
* The GitHub secret used for validating webhook payloads.
Expand All @@ -51,7 +58,7 @@ public class GitHubWebhooksController {
* 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())
Expand Down Expand Up @@ -79,6 +86,11 @@ public void initializeGitHubSecret() {
*/
private LPVSGitHubService gitHubService;

/**
* Service for establishing and managing connections to the GitHub API.
*/
private LPVSGitHubConnectionService gitHubConnectionService;

/**
* LPVSExitHandler for handling application exit scenarios.
*/
Expand All @@ -90,23 +102,26 @@ 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.
* @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.
*/
public GitHubWebhooksController(
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;
Expand Down Expand Up @@ -166,6 +181,68 @@ public ResponseEntity<LPVSResponseWrapper> gitHubWebhooks(
.body(new LPVSResponseWrapper(SUCCESS));
}

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

GitHub gitHub = gitHubConnectionService.connectToGitHubApi();
GHRepository repository = gitHub.getRepository(gitHubOrg + "/" + gitHubRepo);
GHPullRequest pullRequest = repository.getPullRequest(prNumber);
LPVSQueue scanConfig = LPVSWebhookUtil.getGitHubWebhookConfig(repository, pullRequest);

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

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

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

/**
* The string representation of the pull request action.
Expand Down Expand Up @@ -77,6 +82,8 @@ public static LPVSPullRequestAction convertFrom(String action) {
return UPDATE;
} else if (action.equals(RESCAN.getPullRequestAction())) {
return RESCAN;
} else if (action.equals(SINGLE_SCAN.getPullRequestAction())) {
return SINGLE_SCAN;
} else {
return null;
}
Expand Down
Loading

0 comments on commit 28ef2d3

Please sign in to comment.