diff --git a/src/main/java/io/ivyteam/devops/github/GitHubRepoConfigurator.java b/src/main/java/io/ivyteam/devops/github/GitHubRepoConfigurator.java index c0ce977..a898b92 100644 --- a/src/main/java/io/ivyteam/devops/github/GitHubRepoConfigurator.java +++ b/src/main/java/io/ivyteam/devops/github/GitHubRepoConfigurator.java @@ -8,7 +8,7 @@ import io.ivyteam.devops.branch.Branch; import io.ivyteam.devops.branch.BranchRepository; import io.ivyteam.devops.repo.Repo; -import io.ivyteam.devops.securityscanner.ScanTypeEnum; +import io.ivyteam.devops.securityscanner.ScanType; import io.ivyteam.devops.securityscanner.SecurityScannerApiHelper; import io.ivyteam.devops.settings.SettingsManager; @@ -63,7 +63,7 @@ public boolean run() { } if (!repo.isVulnAlertOn() && ghRepo.isPrivate()) { LOGGER.info("Enable Vulnerability-alerts"); - SecurityScannerApiHelper.enableAlerts(ghRepo.getUrl(), gitHub.token(), ScanTypeEnum.DEPENDABOT.getValue()); + SecurityScannerApiHelper.enableAlerts(ghRepo.getUrl(), gitHub.token(), ScanType.DEPENDABOT.getValue()); changed = true; } if (repo.hooks()) { diff --git a/src/main/java/io/ivyteam/devops/github/GitHubSynchronizer.java b/src/main/java/io/ivyteam/devops/github/GitHubSynchronizer.java index 6402f1b..6406b7c 100644 --- a/src/main/java/io/ivyteam/devops/github/GitHubSynchronizer.java +++ b/src/main/java/io/ivyteam/devops/github/GitHubSynchronizer.java @@ -22,7 +22,7 @@ import io.ivyteam.devops.pullrequest.PullRequestRepository; import io.ivyteam.devops.repo.Repo; import io.ivyteam.devops.repo.RepoRepository; -import io.ivyteam.devops.securityscanner.ScanTypeEnum; +import io.ivyteam.devops.securityscanner.ScanType; import io.ivyteam.devops.securityscanner.SecurityScannerApiHelper; import io.ivyteam.devops.securityscanner.SecurityScannerRepository; import io.ivyteam.devops.user.UserRepository; @@ -102,11 +102,11 @@ public synchronized void run() { synch(repo); var helper = new SecurityScannerApiHelper(securityScanners, repo, gitHub.token()); if (repo.isVulnerabilityAlertsEnabled()) { - helper.synch(ScanTypeEnum.DEPENDABOT.getValue()); + helper.synch(ScanType.DEPENDABOT); } if (!repo.isPrivate()) { - helper.synch(ScanTypeEnum.CODE_SCANNING.getValue()); - helper.synch(ScanTypeEnum.SECRET_SCANNING.getValue()); + helper.synch(ScanType.CODE_SCANNING); + helper.synch(ScanType.SECRET_SCANNING); } } diff --git a/src/main/java/io/ivyteam/devops/repo/ReposView.java b/src/main/java/io/ivyteam/devops/repo/ReposView.java index 5999c73..3895c00 100644 --- a/src/main/java/io/ivyteam/devops/repo/ReposView.java +++ b/src/main/java/io/ivyteam/devops/repo/ReposView.java @@ -20,9 +20,10 @@ import io.ivyteam.devops.branch.BranchRepository; import io.ivyteam.devops.pullrequest.PullRequestRepository; -import io.ivyteam.devops.securityscanner.ScanTypeEnum; +import io.ivyteam.devops.securityscanner.ScanType; import io.ivyteam.devops.securityscanner.SecurityScanner; import io.ivyteam.devops.securityscanner.SecurityScannerRepository; +import io.ivyteam.devops.securityscanner.SecurityScannerRepository.Key; import io.ivyteam.devops.view.View; @Route("") @@ -31,6 +32,7 @@ public class ReposView extends View { public ReposView(RepoRepository repos, PullRequestRepository prs, BranchRepository branches, SecurityScannerRepository securityscanners) { + var repositories = repos.all(); grid = new Grid<>(repositories); title.setText("Repositories (" + repositories.size() + ")"); @@ -69,17 +71,12 @@ public ReposView(RepoRepository repos, PullRequestRepository prs, BranchReposito grid .addComponentColumn(repo -> { - if (repo.archived() || repo.privateRepo()) { - return null; - } if (repo.license() != null) { var icon = createIcon(VaadinIcon.CHECK); icon.getElement().getThemeList().add("badge success"); return icon; } - var icon = createIcon(VaadinIcon.CLOSE); - icon.getElement().getThemeList().add("badge error"); - return icon; + return null; }) .setHeader("License") .setWidth("10%") @@ -92,6 +89,11 @@ public ReposView(RepoRepository repos, PullRequestRepository prs, BranchReposito icon.setTooltipText("Archived"); layout.add(icon); } + return layout; + }).setWidth("75px"); + + grid.addComponentColumn(repo -> { + var layout = new HorizontalLayout(); if (repo.privateRepo()) { var icon = createIcon(VaadinIcon.LOCK); icon.setTooltipText("Private"); @@ -100,43 +102,49 @@ public ReposView(RepoRepository repos, PullRequestRepository prs, BranchReposito return layout; }).setWidth("75px"); + var scanners = securityscanners.all(); + grid.addComponentColumn(repo -> { var layout = new HorizontalLayout(); - var dependabot = securityscanners.getByRepoAndScantype(repo.name(), ScanTypeEnum.DEPENDABOT.getValue()); - if (dependabot != null) { - layout.add(createSecurityScannerAnchor(dependabot, dependabot.link_dependabot(), - ScanTypeEnum.DEPENDABOT.getValue())); + var scanner = scanners.get(new Key(repo.name(), ScanType.DEPENDABOT)); + if (scanner != null) { + layout.add(toSecurityScannerLink(scanner)); } return layout; - }).setHeader("Dependabot").setWidth("100px").setSortable(true) - .setComparator(Comparator.comparing( - r -> getSortingNr(securityscanners.getByRepoAndScantype(r.name(), ScanTypeEnum.DEPENDABOT.getValue())))); + }) + .setHeader("Dependabot") + .setWidth("100px") + .setSortable(true) + .setComparator( + Comparator.comparing(repo -> getSortingNr(scanners.get(new Key(repo.name(), ScanType.DEPENDABOT))))); grid.addComponentColumn(repo -> { var layout = new HorizontalLayout(); - var codeScan = securityscanners.getByRepoAndScantype(repo.name(), ScanTypeEnum.CODE_SCANNING.getValue()); - if (codeScan != null) { - layout.add( - createSecurityScannerAnchor(codeScan, codeScan.link_codeScan(), ScanTypeEnum.CODE_SCANNING.getValue())); + var scanner = scanners.get(new Key(repo.name(), ScanType.CODE_SCANNING)); + if (scanner != null) { + layout.add(toSecurityScannerLink(scanner)); } return layout; - }).setHeader("CodeScan").setWidth("100px").setSortable(true) - .setComparator(Comparator.comparing( - r -> getSortingNr(securityscanners.getByRepoAndScantype(r.name(), ScanTypeEnum.CODE_SCANNING.getValue())))); + }) + .setHeader("CodeScan") + .setWidth("100px") + .setSortable(true) + .setComparator( + Comparator.comparing(repo -> getSortingNr(scanners.get(new Key(repo.name(), ScanType.CODE_SCANNING))))); grid.addComponentColumn(repo -> { var layout = new HorizontalLayout(); - var secretScan = securityscanners.getByRepoAndScantype(repo.name(), ScanTypeEnum.SECRET_SCANNING.getValue()); - if (secretScan != null) { - layout.add( - createSecurityScannerAnchor(secretScan, secretScan.link_secretScan(), - ScanTypeEnum.SECRET_SCANNING.getValue())); + var scanner = scanners.get(new Key(repo.name(), ScanType.SECRET_SCANNING)); + if (scanner != null) { + layout.add(toSecurityScannerLink(scanner)); } return layout; - }).setHeader("SecretScan").setWidth("100px").setSortable(true) - .setComparator(Comparator.comparing( - r -> getSortingNr( - securityscanners.getByRepoAndScantype(r.name(), ScanTypeEnum.SECRET_SCANNING.getValue())))); + }) + .setHeader("SecretScan") + .setWidth("100px") + .setSortable(true) + .setComparator( + Comparator.comparing(repo -> getSortingNr(scanners.get(new Key(repo.name(), ScanType.SECRET_SCANNING))))); grid.setHeightFull(); @@ -218,28 +226,21 @@ private int getSortingNr(SecurityScanner s) { if (s == null) { return -1; } - if (s.critical() != 0) { - return (int) (s.critical() + Math.pow(10, 9)); - } - if (s.high() != 0) { - return (int) (s.high() + Math.pow(10, 6)); - } - if (s.medium() != 0) { - return (int) (s.medium() + Math.pow(10, 3)); - } - return s.low(); + return s.sort(); } - private Anchor createSecurityScannerAnchor(SecurityScanner ss, String link, String name) { + private Anchor toSecurityScannerLink(SecurityScanner ss) { int summary = ss.critical() + ss.high() + ss.medium() + ss.low(); - var text = name + "-> C: " + ss.critical() + " | H: " + ss.high() + " | M: " + ss.medium() + " | L: " + ss.low(); + var text = ss.scantype().getValue() + "-> C: " + ss.critical() + " | H: " + ss.high() + " | M: " + ss.medium() + + " | L: " + + ss.low(); - Icon icon = VaadinIcon.QUESTION_CIRCLE.create(); + var icon = VaadinIcon.QUESTION_CIRCLE.create(); icon.setSize("14px"); icon.setTooltipText(text); icon.getStyle().set("margin-left", "4px"); - var a = new Anchor(link, String.valueOf(summary), AnchorTarget.BLANK); + var a = new Anchor(ss.link(), String.valueOf(summary), AnchorTarget.BLANK); a.add(icon); if (ss.critical() + ss.high() > 0) { @@ -250,8 +251,6 @@ private Anchor createSecurityScannerAnchor(SecurityScanner ss, String link, Stri a.getElement().getThemeList().add("badge pill small success"); icon.setIcon(VaadinIcon.CHECK); } - return a; } - } diff --git a/src/main/java/io/ivyteam/devops/securityscanner/ScanTypeEnum.java b/src/main/java/io/ivyteam/devops/securityscanner/ScanType.java similarity index 64% rename from src/main/java/io/ivyteam/devops/securityscanner/ScanTypeEnum.java rename to src/main/java/io/ivyteam/devops/securityscanner/ScanType.java index 3366da3..acb66e1 100644 --- a/src/main/java/io/ivyteam/devops/securityscanner/ScanTypeEnum.java +++ b/src/main/java/io/ivyteam/devops/securityscanner/ScanType.java @@ -1,6 +1,7 @@ package io.ivyteam.devops.securityscanner; -public enum ScanTypeEnum { +public enum ScanType { + DEPENDABOT("dependabot", "/security/dependabot/"), SECRET_SCANNING("secret-scanning", "/security/secret-scanning/"), CODE_SCANNING("code-scanning", "/security/code-scanning/"); @@ -8,7 +9,7 @@ public enum ScanTypeEnum { private final String value; private final String urlSuffix; - ScanTypeEnum(String value, String urlSuffix) { + ScanType(String value, String urlSuffix) { this.value = value; this.urlSuffix = urlSuffix; } @@ -20,4 +21,13 @@ public String getValue() { public String getUrlSuffix() { return urlSuffix; } + + public static ScanType fromValue(String value) { + for (ScanType type : values()) { + if (type.value.equals(value)) { + return type; + } + } + return null; + } } diff --git a/src/main/java/io/ivyteam/devops/securityscanner/SecurityScanner.java b/src/main/java/io/ivyteam/devops/securityscanner/SecurityScanner.java index ef8dee8..7d6e690 100644 --- a/src/main/java/io/ivyteam/devops/securityscanner/SecurityScanner.java +++ b/src/main/java/io/ivyteam/devops/securityscanner/SecurityScanner.java @@ -2,7 +2,7 @@ public record SecurityScanner( String repo, - String scantype, + ScanType scantype, int critical, int high, int medium, @@ -10,15 +10,72 @@ public record SecurityScanner( private final static String URL_PREFIX = "https://github.com/"; - public String link_dependabot() { - return URL_PREFIX + repo + ScanTypeEnum.DEPENDABOT.getUrlSuffix(); + public String link() { + return switch (scantype) { + case DEPENDABOT -> URL_PREFIX + repo + ScanType.DEPENDABOT.getUrlSuffix(); + case SECRET_SCANNING -> URL_PREFIX + repo + ScanType.SECRET_SCANNING.getUrlSuffix(); + case CODE_SCANNING -> URL_PREFIX + repo + ScanType.CODE_SCANNING.getUrlSuffix(); + }; } - public String link_secretScan() { - return URL_PREFIX + repo + ScanTypeEnum.SECRET_SCANNING.getUrlSuffix(); + public int sort() { + if (critical != 0) { + return (int) (critical + Math.pow(10, 9)); + } + if (high != 0) { + return (int) (high + Math.pow(10, 6)); + } + if (medium != 0) { + return (int) (medium + Math.pow(10, 3)); + } + return low; } - public String link_codeScan() { - return URL_PREFIX + repo + ScanTypeEnum.CODE_SCANNING.getUrlSuffix(); + public static Builder create() { + return new Builder(); + } + + public static class Builder { + + private String repo; + private ScanType scantype; + private int critical; + private int high; + private int medium; + private int low; + + public Builder repo(String repo) { + this.repo = repo; + return this; + } + + public Builder scantype(ScanType scantype) { + this.scantype = scantype; + return this; + } + + public Builder critical(int critical) { + this.critical = critical; + return this; + } + + public Builder high(int high) { + this.high = high; + return this; + } + + public Builder medium(int medium) { + this.medium = medium; + return this; + } + + public Builder low(int low) { + this.low = low; + return this; + } + + public SecurityScanner build() { + return new SecurityScanner(repo, scantype, critical, high, medium, low); + } } } diff --git a/src/main/java/io/ivyteam/devops/securityscanner/SecurityScannerApiHelper.java b/src/main/java/io/ivyteam/devops/securityscanner/SecurityScannerApiHelper.java index fa2fa3b..bd516ce 100644 --- a/src/main/java/io/ivyteam/devops/securityscanner/SecurityScannerApiHelper.java +++ b/src/main/java/io/ivyteam/devops/securityscanner/SecurityScannerApiHelper.java @@ -59,8 +59,8 @@ public static void enableAlerts(URL url, String token, String scantype) { } } - private static String getAlerts(URL url, String token, String scantype) { - var apiUrl = toUri(url, "/" + scantype + "/alerts?per_page=100"); + private static String getAlerts(URL url, String token, ScanType scantype) { + var apiUrl = toUri(url, "/" + scantype.getValue() + "/alerts?per_page=100"); try (var client = HttpClient.newHttpClient()) { var request = HttpRequest.newBuilder() .uri(apiUrl) @@ -76,32 +76,32 @@ private static String getAlerts(URL url, String token, String scantype) { if (response.statusCode() == HttpURLConnection.HTTP_NOT_FOUND) { return null; } else { - LOGGER.warn("get " + scantype + " is failed: " + response.statusCode() + ", api/url: " + apiUrl); + LOGGER.warn("get " + scantype.getValue() + " is failed: " + response.statusCode() + ", api/url: " + apiUrl); } } catch (Exception ex) { - LOGGER.warn("Could not get " + scantype + " alerts", ex); + LOGGER.warn("Could not get " + scantype.getValue() + " alerts", ex); } return null; } - private static SecurityScanner parseAlerts(String json, String repoName, String scantype) { + private static SecurityScanner parseAlerts(String json, String repoName, ScanType scantype) { try { JsonNode root = MAPPER.readTree(json); Map severityCounts = new HashMap<>(); - if (scantype.equals(ScanTypeEnum.DEPENDABOT.getValue())) { + if (scantype.equals(ScanType.DEPENDABOT)) { for (JsonNode node : root) { if (node.path("state").asText().equals(ALERT_REQUIRED_STATE)) { severityCounts.merge(node.path("security_vulnerability").path("severity").asText(), 1L, Long::sum); } } - } else if (scantype.equals(ScanTypeEnum.CODE_SCANNING.getValue())) { + } else if (scantype.equals(ScanType.CODE_SCANNING)) { for (JsonNode node : root) { if (node.path("state").asText().equals(ALERT_REQUIRED_STATE)) { severityCounts.merge(node.path("rule").path("security_severity_level").asText(), 1L, Long::sum); } } - } else if (scantype.equals(ScanTypeEnum.SECRET_SCANNING.getValue())) { + } else if (scantype.equals(ScanType.SECRET_SCANNING)) { for (JsonNode node : root) { if (node.path("state").asText().equals(ALERT_REQUIRED_STATE)) { severityCounts.merge((node.path("url").asText() != null ? "high" : null), 1L, Long::sum); @@ -120,7 +120,7 @@ private static SecurityScanner parseAlerts(String json, String repoName, String return null; } - public void synch(String scantype) throws IOException { + public void synch(ScanType scantype) throws IOException { var json = SecurityScannerApiHelper.getAlerts(repo.getUrl(), token, scantype); if (json == null) { return; diff --git a/src/main/java/io/ivyteam/devops/securityscanner/SecurityScannerRepository.java b/src/main/java/io/ivyteam/devops/securityscanner/SecurityScannerRepository.java index 4c2d887..98e18c6 100644 --- a/src/main/java/io/ivyteam/devops/securityscanner/SecurityScannerRepository.java +++ b/src/main/java/io/ivyteam/devops/securityscanner/SecurityScannerRepository.java @@ -2,8 +2,7 @@ import java.sql.ResultSet; import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; @@ -16,51 +15,45 @@ public class SecurityScannerRepository { @Autowired private Database db; - public List all() { - try (var connection = db.connection()) { - try (var stmt = connection.prepareStatement("SELECT * FROM securityscanner ORDER BY repository, scantype")) { - try (var result = stmt.executeQuery()) { - return getSecurityScanner(stmt.executeQuery()); - } - } - } catch (SQLException ex) { - throw new RuntimeException(ex); - } + public SecurityScannerRepository(Database db) { + this.db = db; } - public SecurityScanner getByRepoAndScantype(String repoName, String scantype) { - if (repoName == null || scantype == null) { - return null; - } + public java.util.Map all() { try (var connection = db.connection()) { try (var stmt = connection - .prepareStatement("SELECT * FROM securityscanner where repository = ? and scantype = ?")) { - stmt.setString(1, repoName); - stmt.setString(2, scantype); - var securityScanners = getSecurityScanner(stmt.executeQuery()); - if (!securityScanners.isEmpty()) { - return securityScanners.getFirst(); + .prepareStatement("SELECT * FROM securityscanner")) { + try (var result = stmt.executeQuery()) { + var scanners = new HashMap(); + while (result.next()) { + var scanner = toSecurityScanner(result); + var key = new Key(scanner.repo(), scanner.scantype()); + scanners.put(key, scanner); + } + return scanners; } - return null; } } catch (SQLException ex) { throw new RuntimeException(ex); } } + public record Key(String repo, ScanType scantype) { + } + public void create(SecurityScanner securityScanner) { try (var connection = db.connection()) { try ( var stmt = connection.prepareStatement("DELETE FROM securityscanner WHERE repository = ? and scantype = ?")) { stmt.setString(1, securityScanner.repo()); - stmt.setString(2, securityScanner.scantype()); + stmt.setString(2, securityScanner.scantype().getValue()); stmt.execute(); } try (var stmt = connection.prepareStatement( "INSERT INTO securityscanner (repository, scantype, critical, high, medium, low) VALUES (?, ?, ?, ?, ?, ?)")) { stmt.setString(1, securityScanner.repo()); - stmt.setString(2, securityScanner.scantype()); + stmt.setString(2, securityScanner.scantype().getValue()); stmt.setInt(3, securityScanner.critical()); stmt.setInt(4, securityScanner.high()); stmt.setInt(5, securityScanner.medium()); @@ -73,23 +66,14 @@ public void create(SecurityScanner securityScanner) { } } - private List getSecurityScanner(ResultSet result) { - var securityScanners = new ArrayList(); - try { - while (result.next()) { - var repository = result.getString("repository"); - var scantype = result.getString("scantype"); - var critical = result.getInt("critical"); - var high = result.getInt("high"); - var medium = result.getInt("medium"); - var low = result.getInt("low"); - - var securityScanner = new SecurityScanner(repository, scantype, critical, high, medium, low); - securityScanners.add(securityScanner); - } - } catch (SQLException ex) { - throw new RuntimeException(ex); - } - return securityScanners; + private SecurityScanner toSecurityScanner(ResultSet result) throws SQLException { + return SecurityScanner.create() + .repo(result.getString("repository")) + .scantype(ScanType.fromValue(result.getString("scantype"))) + .critical(result.getInt("critical")) + .high(result.getInt("high")) + .medium(result.getInt("medium")) + .low(result.getInt("low")) + .build(); } } diff --git a/src/test/java/io/ivyteam/devops/securityscanner/TestSecurityScannerRepository.java b/src/test/java/io/ivyteam/devops/securityscanner/TestSecurityScannerRepository.java new file mode 100644 index 0000000..2aa106a --- /dev/null +++ b/src/test/java/io/ivyteam/devops/securityscanner/TestSecurityScannerRepository.java @@ -0,0 +1,72 @@ +package io.ivyteam.devops.securityscanner; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.file.Path; +import java.util.Map; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import io.ivyteam.devops.db.Database; +import io.ivyteam.devops.repo.Repo; +import io.ivyteam.devops.repo.RepoRepository; +import io.ivyteam.devops.securityscanner.SecurityScannerRepository.Key; + +class TestSecurityScannerRepository { + + SecurityScanner SCANNER_1 = SecurityScanner.create() + .critical(5) + .high(6) + .low(2) + .medium(4) + .scantype(ScanType.DEPENDABOT) + .repo("axonivy/test") + .build(); + + SecurityScanner SCANNER_2 = SecurityScanner.create() + .critical(5) + .high(6) + .low(2) + .medium(4) + .scantype(ScanType.SECRET_SCANNING) + .repo("axonivy/test") + .build(); + + SecurityScanner SCANNER_3 = SecurityScanner.create() + .critical(5) + .high(6) + .low(2) + .medium(4) + .scantype(ScanType.SECRET_SCANNING) + .repo("axonivy/vulnerarbilty") + .build(); + + @TempDir + Path tempDir; + + SecurityScannerRepository scanners; + + RepoRepository repos; + + @BeforeEach + void beforeEach() { + var db = new Database(tempDir.resolve("test.db")); + scanners = new SecurityScannerRepository(db); + repos = new RepoRepository(db); + } + + @Test + void all() { + repos.create(Repo.create().name("axonivy/test").build()); + repos.create(Repo.create().name("axonivy/vulnerarbilty").build()); + scanners.create(SCANNER_1); + scanners.create(SCANNER_2); + scanners.create(SCANNER_3); + assertThat(scanners.all()).containsOnly( + Map.entry(new Key("axonivy/test", ScanType.DEPENDABOT), SCANNER_1), + Map.entry(new Key("axonivy/test", ScanType.SECRET_SCANNING), SCANNER_2), + Map.entry(new Key("axonivy/vulnerarbilty", ScanType.SECRET_SCANNING), SCANNER_3)); + } +}