diff --git a/pom.xml b/pom.xml index d2b917c..faeef25 100644 --- a/pom.xml +++ b/pom.xml @@ -1,11 +1,12 @@ - + 4.0.0 org.jenkins-ci.plugins plugin - 4.40 + 4.52 @@ -23,29 +24,30 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.11.0 - 1.8 - 1.8 + 11 + 11 com.github.spotbugs spotbugs-maven-plugin - 4.7.0.0 + 4.8.1.0 com.github.spotbugs spotbugs - 4.7.0 + 4.8.0 org.codehaus.mojo animal-sniffer-maven-plugin + 1.23 org.codehaus.mojo.signature @@ -77,13 +79,14 @@ scm:git:git://github.com/jenkinsci/appspider-build-scanner-plugin.git scm:git:git@github.com:jenkinsci/appspider-build-scanner-plugin.git https://github.com/jenkinsci/appspider-build-scanner-plugin.git - jenkinsci-appspider-plugin-1.0.12 + jenkinsci-appspider-plugin-1.0.16 -Xdoclint:none - 2.348 + 2.375.1 + 11 @@ -112,13 +115,13 @@ org.mockito mockito-core - 3.3.3 + 5.7.0 test org.mockito mockito-junit-jupiter - 3.3.3 + 5.7.0 test @@ -130,7 +133,7 @@ org.json json - 20180130 + 20231013 org.glassfish.jersey.core @@ -147,26 +150,36 @@ jakarta.annotation-api 2.1.0 + + jakarta.inject + jakarta.inject-api + 2.0.0 + + + jakarta.validation + jakarta.validation-api + 3.0.0 + org.apache.httpcomponents - httpcore - 4.4.13 + httpmime + 4.5.12 org.apache.httpcomponents httpclient - 4.5.13 + 4.5.12 + + + org.apache.httpcomponents + httpcore + 4.4.16 org.freemarker freemarker 2.3.30 - - org.apache.httpcomponents - httpmime - 4.5.12 - commons-beanutils commons-beanutils @@ -200,7 +213,7 @@ commons-codec commons-codec - 1.15 + 1.16.0 javax.ws.rs diff --git a/src/main/java/com/rapid7/appspider/ApiSerializer.java b/src/main/java/com/rapid7/appspider/ApiSerializer.java index 5598740..dbd4081 100644 --- a/src/main/java/com/rapid7/appspider/ApiSerializer.java +++ b/src/main/java/com/rapid7/appspider/ApiSerializer.java @@ -8,6 +8,7 @@ import com.rapid7.appspider.datatransferobjects.ScanResult; import freemarker.template.Template; import freemarker.template.TemplateException; +import hudson.model.Api; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -18,13 +19,17 @@ import java.net.URL; import java.util.*; -public class ApiSerializer { +public final class ApiSerializer { private final LoggerFacade logger; - public ApiSerializer(LoggerFacade logger) { - if (logger == null) + public static ApiSerializer createInstanceOrThrow(LoggerFacade logger) { + if (logger == null) { throw new IllegalArgumentException("logger cannot be null"); + } + return new ApiSerializer(logger); + } + private ApiSerializer(LoggerFacade logger) { this.logger = logger; } @@ -67,7 +72,7 @@ public boolean getIsSuccess(JSONObject jsonObject) { public ScanResult getScanResult(JSONObject jsonObject) { try { - return new ScanResult(jsonObject); + return ScanResult.createInstanceFromJsonOrThrow(jsonObject); } catch (IllegalArgumentException e) { logger.severe(e.toString()); return new ScanResult(false, ""); diff --git a/src/main/java/com/rapid7/appspider/ContentHelper.java b/src/main/java/com/rapid7/appspider/ContentHelper.java index b8d49ae..697682b 100644 --- a/src/main/java/com/rapid7/appspider/ContentHelper.java +++ b/src/main/java/com/rapid7/appspider/ContentHelper.java @@ -25,8 +25,6 @@ import java.util.Objects; import java.util.Optional; -import static com.rapid7.appspider.Utility.isSuccessStatusCode; - /** * parsing and serializing helper methods for handling JSONObject manipulation */ @@ -34,9 +32,13 @@ public class ContentHelper { private final LoggerFacade logger; - public ContentHelper(LoggerFacade logger) { + public static ContentHelper createInstanceOrThrow(LoggerFacade logger) { if (Objects.isNull(logger)) throw new IllegalArgumentException("logger cannot be null"); + return new ContentHelper(logger); + } + + private ContentHelper(LoggerFacade logger) { this.logger = logger; } @@ -111,11 +113,11 @@ public NameValuePair pairFrom(String key, String value) { * @return on success an Optional containing a JSONObject; otherwise, Optional.empty() */ public Optional responseToJSONObject(HttpResponse response, String path) { - if (isSuccessStatusCode(response)) { + if (FunctionalUtility.isSuccessStatusCode(response)) { return asJson(response.getEntity()); } - logResponseFailure("request failed", response); + logResponseFailure("request failed " + path, response); return Optional.empty(); } @@ -140,8 +142,8 @@ public Optional asJson(HttpEntity entity) { * @param key key in the json object to serve as key in the map * @param value value in the json object to serve as value in the map * @param optionalJsonObject Optional{JSONObject} to extract key/value pairs from if present - * @return on successs a Map{String, String} of key/value pairs from JSONObject; - * otherwiwse Optional.empty() + * @return on successes a Map{String, String} of key/value pairs from JSONObject; + * otherwise Optional.empty() */ public Optional> asMapOfStringToString(String key, String value, Optional optionalJsonObject) { return optionalJsonObject.flatMap(json -> diff --git a/src/main/java/com/rapid7/appspider/Scan.java b/src/main/java/com/rapid7/appspider/DastScan.java similarity index 93% rename from src/main/java/com/rapid7/appspider/Scan.java rename to src/main/java/com/rapid7/appspider/DastScan.java index 9492e5d..bcf274b 100644 --- a/src/main/java/com/rapid7/appspider/Scan.java +++ b/src/main/java/com/rapid7/appspider/DastScan.java @@ -13,7 +13,7 @@ import java.util.Optional; import java.util.concurrent.TimeUnit; -public class Scan { +public class DastScan { private static final String SUCCESSFUL_SCAN = "Completed|Stopped"; private static final String UNSUCCESSFUL_SCAN = "ReportError"; private static final String FAILED_SCAN = "Failed"; @@ -25,13 +25,17 @@ public class Scan { private final LoggerFacade log; private Optional id; - public Scan(EnterpriseClient client, ScanSettings settings, LoggerFacade log) { + public static DastScan createInstanceOrThrow(EnterpriseClient client, ScanSettings settings, LoggerFacade log) { if (Objects.isNull(client)) throw new IllegalArgumentException("client cannot be null"); if (Objects.isNull(settings)) throw new IllegalArgumentException("settings cannot be null"); if (Objects.isNull(log)) throw new IllegalArgumentException("log cannot be null"); + return new DastScan(client, settings, log); + } + + private DastScan(EnterpriseClient client, ScanSettings settings, LoggerFacade log) { this.client = client; this.settings = settings; this.log = log; @@ -47,7 +51,7 @@ public Optional getId() { public boolean process(AuthenticationModel authModel) throws InterruptedException { Optional maybeAuthToken = client.login(authModel); - if (!maybeAuthToken.isPresent()) { + if (maybeAuthToken.isEmpty()) { log.println(UNAUTHORIZED_ERROR); return false; } @@ -71,7 +75,7 @@ public boolean process(AuthenticationModel authModel) throws InterruptedExceptio waitForScanCompletion(runResult.getScanId(), authModel); maybeAuthToken = client.login(authModel); - if (!maybeAuthToken.isPresent()) { + if (maybeAuthToken.isEmpty()) { log.println(UNAUTHORIZED_ERROR); return false; } @@ -100,7 +104,7 @@ private boolean createScanBeforeRunIfNeeded(String authToken) { log.println("Value of Scan Config Engine Group name: " + settings.getScanConfigEngineGroupName()); Optional engineGroupId = client.getEngineGroupIdFromName(authToken, settings.getScanConfigEngineGroupName()); - if (!engineGroupId.isPresent()) { + if (engineGroupId.isEmpty()) { log.println(String.format("no engine group matching %s was found.", settings.getScanConfigEngineGroupName())); return false; } @@ -144,7 +148,7 @@ private void waitForScanCompletion(String scanId, AuthenticationModel authModel) } private Optional getStatus(String scanId, AuthenticationModel authModel) { Optional authToken = client.login(authModel); - if (!authToken.isPresent()) { + if (authToken.isEmpty()) { log.println(UNAUTHORIZED_ERROR); return Optional.empty(); } diff --git a/src/main/java/com/rapid7/appspider/EnterpriseRestClient.java b/src/main/java/com/rapid7/appspider/EnterpriseRestClient.java index ef76cfb..7c63f29 100644 --- a/src/main/java/com/rapid7/appspider/EnterpriseRestClient.java +++ b/src/main/java/com/rapid7/appspider/EnterpriseRestClient.java @@ -125,7 +125,7 @@ public boolean testAuthentication(AuthenticationModel authModel) { public Optional getEngineGroupNamesForClient(String authToken) { return getEngineGroupsForClient(authToken) .map(map -> new ArrayList<>(map.keySet())) - .map(Utility::toStringArray); + .map(FunctionalUtility::toStringArray); } /** @@ -260,7 +260,7 @@ private Optional getConfigs(String authToken) { public Optional getConfigNames(String authToken) { return getConfigs(authToken) .flatMap(apiSerializer::getConfigNames) - .map(Utility::toStringArray); + .map(FunctionalUtility::toStringArray); } /** diff --git a/src/main/java/com/rapid7/appspider/FreemarkerConfiguration.java b/src/main/java/com/rapid7/appspider/FreemarkerConfiguration.java index 27f92cf..da889e5 100644 --- a/src/main/java/com/rapid7/appspider/FreemarkerConfiguration.java +++ b/src/main/java/com/rapid7/appspider/FreemarkerConfiguration.java @@ -21,14 +21,14 @@ class FreemarkerConfiguration { private final Configuration configuration; /** - * get the singelton instance, initializing it if necessary + * get the singleton instance, initializing it if necessary */ public static FreemarkerConfiguration getInstance() { - return Container.INSTANCE; + return InstanceContainer.CONFIGURATION_INSTANCE; } - private static class Container { - private static final FreemarkerConfiguration INSTANCE = new FreemarkerConfiguration(); + private static class InstanceContainer { + private static final FreemarkerConfiguration CONFIGURATION_INSTANCE = new FreemarkerConfiguration(); } /** diff --git a/src/main/java/com/rapid7/appspider/Utility.java b/src/main/java/com/rapid7/appspider/FunctionalUtility.java similarity index 92% rename from src/main/java/com/rapid7/appspider/Utility.java rename to src/main/java/com/rapid7/appspider/FunctionalUtility.java index 6503142..f594d90 100644 --- a/src/main/java/com/rapid7/appspider/Utility.java +++ b/src/main/java/com/rapid7/appspider/FunctionalUtility.java @@ -10,11 +10,11 @@ import java.util.Objects; /** - * various utility merthods that have no better home + * various utility methods that have no better home */ -public class Utility { +public class FunctionalUtility { - private Utility() { + private FunctionalUtility() { } /** diff --git a/src/main/java/com/rapid7/appspider/HttpClientFactory.java b/src/main/java/com/rapid7/appspider/HttpClientFactory.java index 3719588..b3e7fe6 100644 --- a/src/main/java/com/rapid7/appspider/HttpClientFactory.java +++ b/src/main/java/com/rapid7/appspider/HttpClientFactory.java @@ -24,21 +24,25 @@ public class HttpClientFactory { private final SSLConnectionSocketFactory socketFactory; final SSLContext sslContext; - public HttpClientFactory(boolean allowSelfSignedCertificates) throws SslContextCreationException { + public static HttpClientFactory createInstanceOrThrow(boolean allowSelfSignedCertificates) + throws SslContextCreationException { try { // ignore self-signed certs since we have no control over the server setup and as such can't // enforce proper certificate usage if (allowSelfSignedCertificates) { - sslContext = new SSLContextBuilder() + return new HttpClientFactory(new SSLContextBuilder() .loadTrustMaterial(null, (x509CertChain, authType) -> true) - .build(); + .build()); } else { - sslContext = SSLContexts.createDefault(); + return new HttpClientFactory(SSLContexts.createDefault()); } - } catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) { throw new SslContextCreationException("Unable to configure SSL Context", e); } + } + + private HttpClientFactory(SSLContext context) { + sslContext = context; socketFactory = new SSLConnectionSocketFactory(sslContext, new String[]{"TLSv1.2"}, null, NoopHostnameVerifier.INSTANCE); diff --git a/src/main/java/com/rapid7/appspider/HttpClientService.java b/src/main/java/com/rapid7/appspider/HttpClientService.java index 185c054..e719698 100644 --- a/src/main/java/com/rapid7/appspider/HttpClientService.java +++ b/src/main/java/com/rapid7/appspider/HttpClientService.java @@ -21,22 +21,23 @@ import java.util.Objects; import java.util.Optional; -import static com.rapid7.appspider.Utility.isSuccessStatusCode; - public class HttpClientService implements ClientService { private final HttpClient httpClient; private final LoggerFacade logger; private final ContentHelper contentHelper; - public HttpClientService(HttpClient httpClient, ContentHelper contentHelper, LoggerFacade logger) { + public static HttpClientService createInstanceOrThrow(HttpClient httpClient, ContentHelper contentHelper, LoggerFacade logger) { if (Objects.isNull(httpClient)) throw new IllegalArgumentException("httpClient cannot be null"); if (Objects.isNull(contentHelper)) throw new IllegalArgumentException("jsonHelper cannot be null"); if (Objects.isNull(logger)) throw new IllegalArgumentException("logger cannot be null"); + return new HttpClientService(httpClient, contentHelper, logger); + } + private HttpClientService(HttpClient httpClient, ContentHelper contentHelper, LoggerFacade logger) { this.httpClient = httpClient; this.contentHelper = contentHelper; this.logger = logger; @@ -67,7 +68,7 @@ public Optional executeJsonRequest(HttpRequestBase request) { public Optional executeEntityRequest(HttpRequestBase request) { try { HttpResponse response = httpClient.execute(request); - return isSuccessStatusCode(response) + return FunctionalUtility.isSuccessStatusCode(response) ? Optional.of(response.getEntity()) : Optional.empty(); diff --git a/src/main/java/com/rapid7/appspider/Report.java b/src/main/java/com/rapid7/appspider/Report.java index 77579c1..4610e27 100644 --- a/src/main/java/com/rapid7/appspider/Report.java +++ b/src/main/java/com/rapid7/appspider/Report.java @@ -7,6 +7,7 @@ import hudson.FilePath; import java.io.*; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -27,15 +28,17 @@ public class Report { private final ScanSettings settings; private final LoggerFacade log; - public Report(EnterpriseClient client, ScanSettings settings, LoggerFacade log) { - + public static Report createInstanceOrThrow(EnterpriseClient client, ScanSettings settings, LoggerFacade log) { if (Objects.isNull(client)) throw new IllegalArgumentException("client cannot be null"); if (Objects.isNull(settings)) throw new IllegalArgumentException("settings cannot be null"); if (Objects.isNull(log)) throw new IllegalArgumentException("log cannot be null"); + return new Report(client, settings, log); + } + private Report(EnterpriseClient client, ScanSettings settings, LoggerFacade log) { this.client = client; this.settings = settings; this.log = log; @@ -51,24 +54,24 @@ public boolean saveReport(AuthenticationModel authModel, String scanId, FilePath log.println("Generating xml report and downloading report zip file to:" + directory); Optional maybeAuthToken = client.login(authModel); - if (!maybeAuthToken.isPresent()) { + if (maybeAuthToken.isEmpty()) { log.println("Unauthorized: unable to retrieve vulnerabilities summary and report.zip"); return false; } String authToken = maybeAuthToken.get(); String dateTimeStamp = "_" + getNowAsFormattedString(); - Path vulnerabiltiesFilename = Paths.get(reportFolder, settings.getReportName() + dateTimeStamp + ".xml"); + Path vulnerabilitiesFilename = Paths.get(reportFolder, settings.getReportName() + dateTimeStamp + ".xml"); Path reportZipFilename = Paths.get(reportFolder, settings.getReportName() + dateTimeStamp + ".zip"); - return saveVulnerabilities(authToken, scanId, vulnerabiltiesFilename) && + return saveVulnerabilities(authToken, scanId, vulnerabilitiesFilename) && saveReportZip(authToken, scanId, reportZipFilename); } private boolean saveVulnerabilities(String authToken, String scanId, Path file) { Optional xml = client.getVulnerabilitiesSummaryXml(authToken, scanId); - if (!xml.isPresent()) { + if (xml.isEmpty()) { log.println("Unable to retrieve vulnerabilities summary."); return false; } @@ -82,9 +85,9 @@ private boolean saveReportZip(String authToken, String scanId, Path file) { private boolean saveXmlFile(Path file, String content) { try { if (!Files.exists(file) ) - file = Files.createFile(file); + Files.createFile(file); - try (BufferedWriter writer = Files.newBufferedWriter(file)) { + try (BufferedWriter writer = Files.newBufferedWriter(file, StandardCharsets.UTF_8)) { writer.write(content); writer.flush(); } @@ -100,7 +103,7 @@ private boolean saveInputStreamToFile(Path file, InputStream inputStream) { try { if (!Files.exists(file) ) - file = Files.createFile(file); + Files.createFile(file); try (InputStream bufferedInput = new BufferedInputStream(inputStream); OutputStream outputStream = Files.newOutputStream(file)) { diff --git a/src/main/java/com/rapid7/appspider/datatransferobjects/ScanResult.java b/src/main/java/com/rapid7/appspider/datatransferobjects/ScanResult.java index 8383e3b..97d1dfc 100644 --- a/src/main/java/com/rapid7/appspider/datatransferobjects/ScanResult.java +++ b/src/main/java/com/rapid7/appspider/datatransferobjects/ScanResult.java @@ -11,18 +11,18 @@ public ScanResult(boolean isSuccess, String scanId) { this.isSuccess = isSuccess; this.scanId = scanId; } - public ScanResult(JSONObject jsonObject) { + + public static ScanResult createInstanceFromJsonOrThrow(JSONObject jsonObject) { if (jsonObject == null) throw new IllegalArgumentException("jsonObject cannot be null"); try { - isSuccess = jsonObject.getBoolean("IsSuccess"); - scanId = jsonObject.getJSONObject("Scan").getString("Id"); - + boolean isSuccess = jsonObject.getBoolean("IsSuccess"); + String scanId = jsonObject.getJSONObject("Scan").getString("Id"); + return new ScanResult(isSuccess, scanId); } catch(JSONException e) { - throw new IllegalArgumentException("unexpected error occured parsing scan result", e); + throw new IllegalArgumentException("unexpected error occurred parsing scan result", e); } - } public String getScanId() { diff --git a/src/main/java/com/rapid7/appspider/models/AuthenticationModel.java b/src/main/java/com/rapid7/appspider/models/AuthenticationModel.java index 6de01b5..303c9ef 100644 --- a/src/main/java/com/rapid7/appspider/models/AuthenticationModel.java +++ b/src/main/java/com/rapid7/appspider/models/AuthenticationModel.java @@ -14,23 +14,28 @@ public class AuthenticationModel { private final String username; private final String password; - private final Optional clientId; + private final String clientId; /** * instantiates a new instance of the {@code AuthenticationModel} class with no client Id */ public AuthenticationModel(String username, String password) { - this(username, password, Optional.empty()); + this(username, password, null); + } + + public static AuthenticationModel createInstanceOrThrow(String username, String password, String clientId) { + if (username == null || username.isEmpty() || password == null) { + throw new IllegalArgumentException(); + } + + return new AuthenticationModel(username, password, clientId); } /** * instantiates a new instance of the {@code AuthenticationModel} class ensuring that * username and password are both non-null and non-empty */ - public AuthenticationModel(String username, String password, Optional clientId) { - if (username == null || username.isEmpty() || password == null || password.isEmpty()) { - throw new IllegalArgumentException(); - } + public AuthenticationModel(String username, String password, String clientId) { this.username = username; this.password = password; this.clientId = clientId; @@ -62,7 +67,7 @@ public String getUsername() { * @return true if client id is set; otherwise, false */ public boolean hasClientId() { - return clientId.isPresent(); + return clientId != null; } /** @@ -71,7 +76,11 @@ public boolean hasClientId() { * @throws NoSuchElementException if this instance does not have a clientId */ public String getClientId() throws NoSuchElementException { - return clientId.orElseThrow(NoSuchElementException::new); + if (clientId == null) { + throw new NoSuchElementException(); + } + + return clientId; } diff --git a/src/main/java/com/rapid7/jenkinspider/PostBuildScan.java b/src/main/java/com/rapid7/jenkinspider/PostBuildScan.java index 6e229c4..237e0da 100644 --- a/src/main/java/com/rapid7/jenkinspider/PostBuildScan.java +++ b/src/main/java/com/rapid7/jenkinspider/PostBuildScan.java @@ -4,12 +4,11 @@ import com.rapid7.appspider.datatransferobjects.ClientIdNamePair; import com.rapid7.appspider.models.AuthenticationModel; +import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; import hudson.FilePath; import hudson.Launcher; -import hudson.model.AbstractBuild; -import hudson.model.AbstractProject; -import hudson.model.BuildListener; +import hudson.model.*; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.BuildStepMonitor; import hudson.tasks.Notifier; @@ -20,6 +19,7 @@ import jenkins.model.Jenkins; import org.apache.commons.validator.routines.UrlValidator; import org.apache.http.impl.client.CloseableHttpClient; +import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; @@ -44,7 +44,7 @@ */ public class PostBuildScan extends Notifier { - private String clientName; // Not set to final since it may change + private final String clientName; // Not set to final since it may change private final String configName; // Not set to final since it may change // if user decided to create a new scan config @@ -135,15 +135,14 @@ public boolean perform(AbstractBuild build, Launcher launcher, BuildListen log.println("Value of Allow Self-Signed certificate : " + allowSelfSignedCertificate); try { - ContentHelper contentHelper = new ContentHelper(log); + ContentHelper contentHelper = ContentHelper.createInstanceOrThrow(log); EnterpriseRestClient client = new EnterpriseRestClient( - new HttpClientService(new HttpClientFactory(allowSelfSignedCertificate).getClient(), contentHelper, - log), - appSpiderEntUrl, new ApiSerializer(log), contentHelper, log); + HttpClientService.createInstanceOrThrow(HttpClientFactory.createInstanceOrThrow(allowSelfSignedCertificate).getClient(), contentHelper, log), + appSpiderEntUrl, ApiSerializer.createInstanceOrThrow(log), contentHelper, log); ScanSettings settings = new ScanSettings(configName, reportName, true, generateReport, scanConfigName, scanConfigUrl, scanConfigEngineGroupName); - Scan scan = new Scan(client, settings, log); + DastScan scan = DastScan.createInstanceOrThrow(client, settings, log); if (!scan.process(authModel)) return false; @@ -158,7 +157,7 @@ public boolean perform(AbstractBuild build, Launcher launcher, BuildListen return false; } - return new Report(client, settings, log).saveReport(authModel, scanId, filePath); + return Report.createInstanceOrThrow(client, settings, log).saveReport(authModel, scanId, filePath); } catch (IllegalArgumentException | SslContextCreationException e) { log.println(e.toString()); @@ -215,8 +214,8 @@ public DescriptorImp() { * prevent the form from being saved. It just means that a message will * be displayed to the user. */ - public FormValidation doCheckappSpiderEntUrl(@QueryParameter String value) { - if (value.length() == 0) + public FormValidation doCheckAppSpiderEntUrl(@QueryParameter String value) { + if (value.isEmpty()) return FormValidation.error("Please set a value"); if (value.length() < 4) return FormValidation.warning("Isn't the value too short?"); @@ -234,6 +233,7 @@ public boolean isApplicable(Class aClass) { /** * @return Display Name of the plugin */ + @NonNull @Override public String getDisplayName() { return "Scan build using AppSpider"; @@ -307,8 +307,8 @@ public void setAppSpiderClientName(String appSpiderClientName) { public AuthenticationModel buildAuthenticationModel() { return appSpiderClientId != null && !appSpiderClientId.isEmpty() && appSpiderEnableMultiClientOrSysAdmin - ? new AuthenticationModel(appSpiderUsername, Secret.toString(appSpiderPassword), Optional.of(appSpiderClientId)) - : new AuthenticationModel(appSpiderUsername, Secret.toString(appSpiderPassword), Optional.empty()); + ? new AuthenticationModel(appSpiderUsername, Secret.toString(appSpiderPassword), appSpiderClientId) + : new AuthenticationModel(appSpiderUsername, Secret.toString(appSpiderPassword), null); } private LoggerFacade buildLoggerFacade() { @@ -359,11 +359,11 @@ public boolean isVerboseEnabled() { private EnterpriseRestClient buildEnterpriseClient(CloseableHttpClient httpClient, String endpoint) { LoggerFacade logger = buildLoggerFacade(); - ContentHelper contentHelper = new ContentHelper(logger); + ContentHelper contentHelper = ContentHelper.createInstanceOrThrow(logger); return new EnterpriseRestClient( - new HttpClientService(httpClient, contentHelper, logger), + HttpClientService.createInstanceOrThrow(httpClient, contentHelper, logger), endpoint, - new ApiSerializer(logger), + ApiSerializer.createInstanceOrThrow(logger), contentHelper, logger); } @@ -381,8 +381,16 @@ public boolean configure(StaplerRequest req, net.sf.json.JSONObject formData) th * all the available scan configs * @return ListBoxModel containing the scan config names */ - public ListBoxModel doFillClientNameItems() throws InterruptedException { - Map idToNames = getClientIdNamePairsWithRetry(NUMBER_OF_GET_CLIENT_ATTEMPTS, DELAY_BETWEEN_GET_CLIENT_ATTEMPTS) + public ListBoxModel doFillClientNameItems(@AncestorInPath Item item) throws InterruptedException { + if (item == null) { // no context + return emptyListBoxModel("[Select a client name]"); + } + boolean hasConfigurePermission = item.hasPermission(Item.CONFIGURE); + if (!hasConfigurePermission) { + return emptyListBoxModel("[Select a client name]"); + } + + Map idToNames = getClientIdNamePairsWithRetry() .stream() .collect(Collectors.toMap(ClientIdNamePair::getName, ClientIdNamePair::getId)); this.clientIdToNames = Optional.of(idToNames); @@ -405,10 +413,15 @@ public ListBoxModel doFillClientNameItems() throws InterruptedException { * all the available scan configs * @return ListBoxModel containing the scan config names */ - public ListBoxModel doFillConfigNameItems(@QueryParameter String clientName) { + public ListBoxModel doFillConfigNameItems(@QueryParameter String clientName, @AncestorInPath Item item) { + if (item == null) { // no context + return emptyListBoxModel("[Select a scan config name]"); + } + boolean hasConfigurePermission = item.hasPermission(Item.CONFIGURE); - if (clientName == null || clientName.equals(CLIENT_NAME_PLACEHOLDER_TEXT) || !clientIdToNames.isPresent()){ - return buildListBoxModel("[Select an engine group name]", new String[0]); + if (!hasConfigurePermission || clientName == null || clientName.equals(CLIENT_NAME_PLACEHOLDER_TEXT) || + clientIdToNames.isEmpty()){ + return emptyListBoxModel("[Select a scan config name]"); } appSpiderClientId = ""; @@ -424,11 +437,21 @@ public ListBoxModel doFillConfigNameItems(@QueryParameter String clientName) { * all the available scan engine groups * @return ListBoxModel containing engine details */ - public ListBoxModel doFillScanConfigEngineGroupNameItems() { + public ListBoxModel doFillScanConfigEngineGroupNameItems(@AncestorInPath Item item) { + if (item == null) { // no context + return emptyListBoxModel("[Select an engine group name]"); + } + boolean hasConfigurePermission = item.hasPermission(Item.CONFIGURE); + if (!hasConfigurePermission) { + return emptyListBoxModel("[Select an engine group name]"); + } scanConfigEngines = getEngineGroups(); return buildListBoxModel("[Select an engine group name]", scanConfigEngines); } + private static ListBoxModel emptyListBoxModel(String introduction) { + return buildListBoxModel(introduction, new String[0]); + } private static ListBoxModel buildListBoxModel(String introduction, String[] items) { ListBoxModel model = new ListBoxModel(); model.add(introduction); // Adding a default "Pick a scan configuration" entry @@ -468,7 +491,7 @@ public FormValidation doTestCredentials(@QueryParameter("appSpiderAllowSelfSigne public FormValidation doValidateNewScanConfig(@QueryParameter("scanConfigName") final String scanConfigName, @QueryParameter("scanConfigUrl") final String scanConfigUrl) { try { - final String ALPHANUMERIC_REGEX = "^[a-zA-Z0_\\-\\.]*$"; + final String ALPHANUMERIC_REGEX = "^[a-zA-Z0_\\-.]*$"; if (!scanConfigName.matches(ALPHANUMERIC_REGEX) || scanConfigName.contains(" ") || scanConfigName.isEmpty()) { @@ -486,8 +509,8 @@ public FormValidation doValidateNewScanConfig(@QueryParameter("scanConfigName") return FormValidation.ok("Valid scan configuration name and url."); } catch (IOException /* | MalformedURLException */ e) { buildLoggerFacade().println(e.getMessage() + " from doValidateNewScanConfig"); - return FormValidation.error("Unable to connect to \"" + scanConfigUrl +"\". Try again in a few mins or " + - "try another url"); + return FormValidation.error("Unable to connect to \"" + scanConfigUrl + + "\". Try again in a few minutes or try another url"); } } @@ -506,14 +529,14 @@ private String[] getConfigNames() { new String[0]); } - private List getClientIdNamePairsWithRetry(int attempts, long delayInMilliseconds) throws InterruptedException { + private List getClientIdNamePairsWithRetry() throws InterruptedException { - for (int i = 0; i< attempts; i++) { + for (int i = 0; i< DescriptorImp.NUMBER_OF_GET_CLIENT_ATTEMPTS; i++) { List pairs = getClientIdNamePairs(); if (!pairs.isEmpty()) { return pairs; } - Thread.sleep(delayInMilliseconds); + Thread.sleep(DescriptorImp.DELAY_BETWEEN_GET_CLIENT_ATTEMPTS); } return Collections.emptyList(); @@ -541,7 +564,7 @@ private T executeRequest(String endpoint, boolean appSpiderAllowSelfSignedCe if (Objects.isNull(supplier)) return errorResult; - try (CloseableHttpClient httpClient = new HttpClientFactory(appSpiderAllowSelfSignedCertificate).getClient()) { + try (CloseableHttpClient httpClient = HttpClientFactory.createInstanceOrThrow(appSpiderAllowSelfSignedCertificate).getClient()) { EnterpriseClient client = buildEnterpriseClient(httpClient, endpoint); return supplier.apply(client); } catch (IOException | SslContextCreationException e) { @@ -554,15 +577,14 @@ private T executeRequestWithAuthorization(AuthorizedRequest request, T er if (Objects.isNull(request)) return errorResult; - try (CloseableHttpClient httpClient = new HttpClientFactory(appSpiderAllowSelfSignedCertificate).getClient()) { + try (CloseableHttpClient httpClient = HttpClientFactory.createInstanceOrThrow(appSpiderAllowSelfSignedCertificate).getClient()) { EnterpriseClient client = buildEnterpriseClient(httpClient, appSpiderEntUrl); if (Objects.isNull(appSpiderPassword)) { return errorResult; } Optional maybeAuthKey = client.login(buildAuthenticationModel()); - if (!maybeAuthKey.isPresent()) { - FormValidation.error("Unauthorized"); + if (maybeAuthKey.isEmpty()) { return errorResult; } return request.executeRequest(client, maybeAuthKey.get()); diff --git a/src/test/java/com/rapid7/appspider/EnterpriseClientTestContext.java b/src/test/java/com/rapid7/appspider/EnterpriseClientTestContext.java index dfa017b..0e1ec9b 100644 --- a/src/test/java/com/rapid7/appspider/EnterpriseClientTestContext.java +++ b/src/test/java/com/rapid7/appspider/EnterpriseClientTestContext.java @@ -22,7 +22,7 @@ import java.util.*; import java.util.stream.Collectors; -import static com.rapid7.appspider.Utility.toStringArray; +import static com.rapid7.appspider.FunctionalUtility.toStringArray; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -47,13 +47,13 @@ public class EnterpriseClientTestContext implements AutoCloseable { private final HttpClient mockHttpClient; private String expectedAuthToken; - private String configId; - private String configName; - private String clientId; - private String clientName; + private final String configId; + private final String configName; + private final String clientId; + private final String clientName; private String expectedScanId; private EnterpriseClient enterpriseClient; - private String url; + private final String url; private Map expectedEngineGroupsIdsByName; private Map expectedEngineGroupsNamesForClient; private final List engineGroupDetails; @@ -61,8 +61,8 @@ public class EnterpriseClientTestContext implements AutoCloseable { EnterpriseClientTestContext(String url) { this.url = url; mockLogger = mock(LoggerFacade.class); - mockContentHelper = new ContentHelper(mockLogger); - mockApiSerializer = new ApiSerializer(mockLogger); + mockContentHelper = ContentHelper.createInstanceOrThrow(mockLogger); + mockApiSerializer = ApiSerializer.createInstanceOrThrow(mockLogger); expectedAuthToken = ""; // set by isSuccess state of each test, just being reset here expectedScanId = ""; configId = "3249E3F6-3B33-4D4E-93EB-2F464AB424A8"; @@ -157,7 +157,7 @@ public EnterpriseClientTestContext arrangeExpectedValues(boolean isSuccess) { } public EnterpriseClientTestContext configureEnterpriseClient() { - enterpriseClient = new EnterpriseRestClient(new HttpClientService(mockHttpClient, mockContentHelper, mockLogger), url, mockApiSerializer, mockContentHelper, mockLogger); + enterpriseClient = new EnterpriseRestClient(HttpClientService.createInstanceOrThrow(mockHttpClient, mockContentHelper, mockLogger), url, mockApiSerializer, mockContentHelper, mockLogger); return this; } diff --git a/src/test/java/com/rapid7/appspider/datatransferobjects/ScanResultTests.java b/src/test/java/com/rapid7/appspider/datatransferobjects/ScanResultTests.java index 1633437..eca6e7f 100644 --- a/src/test/java/com/rapid7/appspider/datatransferobjects/ScanResultTests.java +++ b/src/test/java/com/rapid7/appspider/datatransferobjects/ScanResultTests.java @@ -6,6 +6,7 @@ import static org.mockito.Mockito.when; import org.json.JSONObject; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -19,33 +20,28 @@ class ScanResultTests { @Mock private JSONObject jsonObject; - @Test - void ctor_throwsIllegalArgumentException_whenJsonObjectIsNull() { - assertThrows(IllegalArgumentException.class, () -> new ScanResult((JSONObject) null)); - } - @Test void ctor_throwsIllegalArgumentException_whenJsonObjectThrowsJSONException() { when(jsonObject.getBoolean(any(String.class))).thenThrow(new JSONException("test")); - assertThrows(IllegalArgumentException.class, () -> new ScanResult(jsonObject)); + assertThrows(IllegalArgumentException.class, () -> ScanResult.createInstanceFromJsonOrThrow(jsonObject)); } @Test void ctor_throwsIllegalArgumentException_whenReturnsEmptyObject() { when(jsonObject.getJSONObject("Scan")).thenReturn(new JSONObject()); - assertThrows(IllegalArgumentException.class, () -> new ScanResult(jsonObject)); + assertThrows(IllegalArgumentException.class, () -> ScanResult.createInstanceFromJsonOrThrow(jsonObject)); } @Test void getScanId_returnsGivenScanId() { ScanResult result = new ScanResult(true, "id"); - assertEquals("id", result.getScanId()); + Assertions.assertEquals("id", result.getScanId()); } @Test void isSuccess_returnsGivenIsSuccess() { ScanResult result = new ScanResult(true, "id"); - assertEquals(true, result.isSuccess()); + Assertions.assertTrue(result.isSuccess()); } @Test @@ -53,9 +49,9 @@ void getScanId_returnsGivenScanIdInJSON() { when(jsonObject.getJSONObject("Scan")).thenReturn(jsonObject); when(jsonObject.getString("Id")).thenReturn("id"); - ScanResult result = new ScanResult(jsonObject); + ScanResult result = ScanResult.createInstanceFromJsonOrThrow(jsonObject); - assertEquals("id", result.getScanId()); + Assertions.assertEquals("id", result.getScanId()); } @Test @@ -64,8 +60,8 @@ void isSuccess_returnsGivenIsSuccessInJSON() { when(jsonObject.getString("Id")).thenReturn("id"); when(jsonObject.getBoolean("IsSuccess")).thenReturn(true); - ScanResult result = new ScanResult(jsonObject); + ScanResult result = ScanResult.createInstanceFromJsonOrThrow(jsonObject); - assertEquals(true, result.isSuccess()); + Assertions.assertTrue(result.isSuccess()); } }