diff --git a/pom.xml b/pom.xml index 8ce11f6a..8e897012 100644 --- a/pom.xml +++ b/pom.xml @@ -42,9 +42,9 @@ jannik.hollenbach@iteratec.com - robert.seedorff - Robert Seedorff - robert.seedorff@iteratec.com + robert.felber + Robert Felber + robert.felber@iteratec.com johannes.zahn diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/config/Config.java b/src/main/java/io/securecodebox/persistence/defectdojo/config/Config.java index 3a54ce56..1ba06f6c 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/config/Config.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/config/Config.java @@ -20,6 +20,10 @@ public final class Config { * Default for {@link #maxPageCountForGets} */ static final int DEFAULT_MAX_PAGE_COUNT_FOR_GETS = 100; + /** + * Null pattern object. + */ + public static final Config NULL = new Config("", "", DEFAULT_MAX_PAGE_COUNT_FOR_GETS); /** * URL of the host which serves the DefectDojo API. diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/http/Foo.java b/src/main/java/io/securecodebox/persistence/defectdojo/http/Foo.java new file mode 100644 index 00000000..f185ee99 --- /dev/null +++ b/src/main/java/io/securecodebox/persistence/defectdojo/http/Foo.java @@ -0,0 +1,78 @@ +// SPDX-FileCopyrightText: the secureCodeBox authors +// +// SPDX-License-Identifier: Apache-2.0 + +package io.securecodebox.persistence.defectdojo.http; + +import io.securecodebox.persistence.defectdojo.config.Config; +import lombok.NonNull; +import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.ProxyAuthenticationStrategy; +import org.springframework.http.HttpHeaders; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +/** + * Placeholder to move duplicated code, will be named better later + */ +public final class Foo { + private final Config config; + + public Foo(@NonNull final Config config) { + super(); + this.config = config; + } + + public HttpHeaders getDefectDojoAuthorizationHeaders() { + HttpHeaders headers = new HttpHeaders(); + headers.set("Authorization", "Token " + this.config.getApiKey()); + + String username = System.getProperty("http.proxyUser", ""); + String password = System.getProperty("http.proxyPassword", ""); + + if (!username.isEmpty() || !password.isEmpty()) { + System.out.println("Setting Proxy Auth Header..."); + headers.set(HttpHeaders.PROXY_AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString((username + ':' + password).getBytes(StandardCharsets.UTF_8))); + } + + return headers; + } + + public RestTemplate setupRestTemplate() { + RestTemplate restTemplate; + + if (System.getProperty("http.proxyUser") != null && System.getProperty("http.proxyPassword") != null) { + // Configuring Proxy Authentication explicitly as it isn't done by default for spring rest templates :( + CredentialsProvider credsProvider = new BasicCredentialsProvider(); + credsProvider.setCredentials( + new AuthScope(System.getProperty("http.proxyHost"), Integer.parseInt(System.getProperty("http.proxyPort"))), + new UsernamePasswordCredentials(System.getProperty("http.proxyUser"), System.getProperty("http.proxyPassword")) + ); + HttpClientBuilder clientBuilder = HttpClientBuilder.create(); + + clientBuilder.useSystemProperties(); + clientBuilder.setProxy(new HttpHost(System.getProperty("http.proxyHost"), Integer.parseInt(System.getProperty("http.proxyPort")))); + clientBuilder.setDefaultCredentialsProvider(credsProvider); + clientBuilder.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()); + + CloseableHttpClient client = clientBuilder.build(); + + HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); + factory.setHttpClient(client); + restTemplate = new RestTemplate(factory); + } else { + restTemplate = new RestTemplate(); + } + + return restTemplate; + } +} diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/http/MissingProxyConfigValue.java b/src/main/java/io/securecodebox/persistence/defectdojo/http/MissingProxyConfigValue.java new file mode 100644 index 00000000..2324bc69 --- /dev/null +++ b/src/main/java/io/securecodebox/persistence/defectdojo/http/MissingProxyConfigValue.java @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: the secureCodeBox authors +// +// SPDX-License-Identifier: Apache-2.0 + +package io.securecodebox.persistence.defectdojo.http; + +import lombok.NonNull; + +/** + * This exception indicates a missing proxy config value + */ +public final class MissingProxyConfigValue extends RuntimeException { + MissingProxyConfigValue(@NonNull final ProxyConfigNames name) { + super(String.format("Expected system property '%s' not set!", name.getLiterat())); + } +} diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/http/ProxyConfig.java b/src/main/java/io/securecodebox/persistence/defectdojo/http/ProxyConfig.java new file mode 100644 index 00000000..9729f675 --- /dev/null +++ b/src/main/java/io/securecodebox/persistence/defectdojo/http/ProxyConfig.java @@ -0,0 +1,88 @@ +// SPDX-FileCopyrightText: the secureCodeBox authors +// +// SPDX-License-Identifier: Apache-2.0 + +package io.securecodebox.persistence.defectdojo.http; + +import lombok.Builder; +import lombok.Value; + +/** + * Holds HTTP proxy configuration + *

+ * This class is immutable by design and therefor thread safe. As defaults it does not use |{@code null} to prevent null + * pointer exceptions. It utilizes sane defaults (empty string or 0) to indicate a not set value. Also it introduces a + * null-object to indicate a not-existing configuration. + *

+ */ +@Value +@Builder +public class ProxyConfig { + /** + * Null pattern object. + */ + public static final ProxyConfig NULL = ProxyConfig.builder().build(); + private static final String DEFAULT_STRING = ""; + private static final int DEFAULT_INT = 0; + + /** + * Username to authenticate on a proxy. + *

+ * Defaults to empty string. + *

+ */ + @Builder.Default + String user = DEFAULT_STRING; + + /** + * Password to authenticate on a proxy. + *

+ * Defaults to empty string. + *

+ */ + @Builder.Default + String password = DEFAULT_STRING; + + /** + * Host name of the proxy. + *

+ * Defaults to empty string. + *

+ */ + @Builder.Default + String host = DEFAULT_STRING; + + /** + * Port of the proxy. + *

+ * Defaults to 0 (zero). + *

+ */ + @Builder.Default + int port = DEFAULT_INT; + + /** + * configuration is considered complete if all values are not default values + * + * @return {@code true} if all values are set else {@code false} + */ + public boolean isComplete() { + if (getUser().equals(DEFAULT_STRING)) { + return false; + } + + if (getPassword().equals(DEFAULT_STRING)) { + return false; + } + + if (getHost().equals(DEFAULT_STRING)) { + return false; + } + + if (getPort() == DEFAULT_INT) { + return false; + } + + return true; + } +} diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/http/ProxyConfigFactory.java b/src/main/java/io/securecodebox/persistence/defectdojo/http/ProxyConfigFactory.java new file mode 100644 index 00000000..0358726e --- /dev/null +++ b/src/main/java/io/securecodebox/persistence/defectdojo/http/ProxyConfigFactory.java @@ -0,0 +1,75 @@ +// SPDX-FileCopyrightText: the secureCodeBox authors +// +// SPDX-License-Identifier: Apache-2.0 + +package io.securecodebox.persistence.defectdojo.http; + +import lombok.NonNull; + +/** + * This class is responsible to create a proxy configuration + *

+ * This implementation collects the configuration values from Java system properties. It also treats the + * cases of non-present values ot values of incorrect type. + *

+ *

+ * This class does not validate for semantic errors of the values, e.g. malformed hostnames or invalid + * port numbers. + *

+ */ +public final class ProxyConfigFactory { + private final SystemPropertyFinder properties = new SystemPropertyFinder(); + + public ProxyConfig create() { + final var builder = ProxyConfig.builder(); + + if (properties.notHasProperty(ProxyConfigNames.HTTP_PROXY_USER)) { + throw new MissingProxyConfigValue(ProxyConfigNames.HTTP_PROXY_USER); + } + + builder.user(properties.getProperty(ProxyConfigNames.HTTP_PROXY_USER)); + + if (properties.notHasProperty(ProxyConfigNames.HTTP_PROXY_PASSWORD)) { + throw new MissingProxyConfigValue(ProxyConfigNames.HTTP_PROXY_PASSWORD); + } + + builder.password(properties.getProperty(ProxyConfigNames.HTTP_PROXY_PASSWORD)); + + if (properties.notHasProperty(ProxyConfigNames.HTTP_PROXY_HOST)) { + throw new MissingProxyConfigValue(ProxyConfigNames.HTTP_PROXY_HOST); + } + + builder.host(properties.getProperty(ProxyConfigNames.HTTP_PROXY_HOST)); + + if (properties.notHasProperty(ProxyConfigNames.HTTP_PROXY_PORT)) { + throw new MissingProxyConfigValue(ProxyConfigNames.HTTP_PROXY_PORT); + } + + try { + builder.port(Integer.parseInt(properties.getProperty(ProxyConfigNames.HTTP_PROXY_PORT))); + } catch (final NumberFormatException e) { + throw new IllegalArgumentException( + String.format("Given port for proxy authentication configuration (property '%s') is not a valid number! Given value wa '%s'.", + ProxyConfigNames.HTTP_PROXY_PORT.getLiterat(), + System.getProperty("http.proxyPort")), + e); + } + + return builder.build(); + } + + private static class SystemPropertyFinder { + private boolean hasProperty(@NonNull final ProxyConfigNames name) { + return System.getProperty(name.getLiterat()) != null; + } + + private boolean notHasProperty(@NonNull final ProxyConfigNames name) { + return !hasProperty(name); + } + + private String getProperty(@NonNull final ProxyConfigNames name) { + return System.getProperty(name.getLiterat()); + } + } + +} diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/http/ProxyConfigNames.java b/src/main/java/io/securecodebox/persistence/defectdojo/http/ProxyConfigNames.java new file mode 100644 index 00000000..85dd2f35 --- /dev/null +++ b/src/main/java/io/securecodebox/persistence/defectdojo/http/ProxyConfigNames.java @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: the secureCodeBox authors +// +// SPDX-License-Identifier: Apache-2.0 + +package io.securecodebox.persistence.defectdojo.http; + +import lombok.Getter; + +/** + * These properties can be configured by passing them to the running Java process w/ flag {@literal -D} + *

+ * Example: {@literal java -Dhttp.proxyHost=... -D... -jar ...} + *

+ *

+ * Important: All four parameters are mandatory. You must set them all + * or none of them! + *

+ */ +@Getter +public enum ProxyConfigNames { + /** + * System property name for the proxy username + */ + HTTP_PROXY_USER("http.proxyUser"), + /** + * System property name for the proxy user's password + */ + HTTP_PROXY_PASSWORD("http.proxyPassword"), + /** + * System property name for the proxy's hostname + */ + HTTP_PROXY_HOST("http.proxyHost"), + /** + * System property for the proxy's port number + */ + HTTP_PROXY_PORT("http.proxyPort"); + + private final String literat; + + ProxyConfigNames(String literat) { + this.literat = literat; + } +} diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/model/ScanFile.java b/src/main/java/io/securecodebox/persistence/defectdojo/model/ScanFile.java index 38047538..6eb8b511 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/model/ScanFile.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/model/ScanFile.java @@ -8,18 +8,24 @@ @Data public class ScanFile { + /** + * A default name must be set + *

+ * It does not matter however unless the parser pays attention to file endings like json or xml. + *

+ */ + static final String DEFAULT_NAME = "default-name.txt"; String content; - // a default name must be set, it does not matter however - // unless the parser pays attention to file endings like json or xml - String name = "default-name.txt"; + String name; - public ScanFile(String content){ - this.content = content; + public ScanFile(String content) { + this(content, DEFAULT_NAME); } - public ScanFile(String content, String name){ + public ScanFile(String content, String name) { + super(); this.content = content; this.name = name; } diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/service/DefaultImportScanService.java b/src/main/java/io/securecodebox/persistence/defectdojo/service/DefaultImportScanService.java index 83f74a9e..0b92bec7 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/service/DefaultImportScanService.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/service/DefaultImportScanService.java @@ -7,6 +7,7 @@ import io.securecodebox.persistence.defectdojo.ScanType; import io.securecodebox.persistence.defectdojo.config.Config; import io.securecodebox.persistence.defectdojo.exception.PersistenceException; +import io.securecodebox.persistence.defectdojo.http.ProxyConfig; import io.securecodebox.persistence.defectdojo.model.ScanFile; import lombok.Getter; import lombok.NonNull; @@ -44,21 +45,23 @@ class DefaultImportScanService implements ImportScanService { new FormHttpMessageConverter(), new ResourceHttpMessageConverter(), new MappingJackson2HttpMessageConverter()); - private final HttpRequester requester = new DefaultHttpRequester(); @Getter private final String defectDojoUrl; @Getter private final String defectDojoApiKey; + private final ProxyConfig proxyConfig; /** * Dedicated constructor. * - * @param config not {@code null} + * @param config not {@code null} + * @param proxyConfig not {@code null} */ - DefaultImportScanService(final @NonNull Config config) { + DefaultImportScanService(final @NonNull Config config, @NonNull ProxyConfig proxyConfig) { super(); this.defectDojoUrl = config.getUrl(); this.defectDojoApiKey = config.getApiKey(); + this.proxyConfig = proxyConfig; } @Override @@ -85,7 +88,7 @@ private ImportScanResponse createFindings(ScanFile scanFile, String endpoint, lo // 2. the raw scan result as file headers.setContentType(MediaType.MULTIPART_FORM_DATA); - // FIXME: Why do we use a multi value map here? Do we need multiple values for any given key? + // FIXME: #36 Why do we use a multi value map here? Do we need multiple values for any given key? final var body = new LinkedMultiValueMap(); body.add("lead", Long.toString(lead)); @@ -100,7 +103,7 @@ private ImportScanResponse createFindings(ScanFile scanFile, String endpoint, lo body.remove(optionName); } - // FIXME: Workaround due to type incompatibility of MultiValueMap and MultiValueMap. + // FIXME: #36 Workaround due to type incompatibility of MultiValueMap and MultiValueMap. for (final var option : options.entrySet()) { body.add(option.getKey(), option.getValue()); } @@ -114,17 +117,30 @@ public String getFilename() { } }; - // FIXME: Why do we add the whole byte array resource here as object? Is not simply the file name sufficient here? Then we could use // We send the whole file content, so DefectDojo can parse the finding by itself. body.add("file", contentsAsResource); final var payload = new HttpEntity>(body, headers); - return requester.exchange(generateApiUrl(endpoint), payload); + return exchangeRequest(endpoint, payload); } catch (HttpClientErrorException e) { throw new PersistenceException("Failed to attach findings to engagement."); } } + ImportScanResponse exchangeRequest(String endpoint, HttpEntity payload) { + final var restTemplate = this.createRestTemplate(); + return restTemplate.exchange( + generateApiUrl(endpoint), + HttpMethod.POST, + payload, + ImportScanResponse.class) + .getBody(); + } + + String generateApiUrl(final String endpoint) { + return String.format("%s/api/v2/%s/", getDefectDojoUrl(), endpoint); + } + /** * The DefectDojo Authentication Header * @@ -136,133 +152,55 @@ HttpHeaders createDefectDojoAuthorizationHeaders() { return authorizationHeader; } - String generateApiUrl(final String endpoint) { - return String.format("%s/api/v2/%s/", getDefectDojoUrl(), endpoint); - } + private RestTemplate createRestTemplate() { + final var template = new RestTemplate(); - private static class SystemPropertyFinder { - private boolean hasProperty(@NonNull final ProxyConfigNames name) { - return System.getProperty(name.getLiterat()) != null; + if (shouldConfigureProxySettings()) { + template.setRequestFactory(createRequestFactoryWithProxyAuthConfig()); } - private boolean notHasProperty(@NonNull final ProxyConfigNames name) { - return !hasProperty(name); - } + template.setMessageConverters(HTTP_MESSAGE_CONVERTERS); - private String getProperty(@NonNull final ProxyConfigNames name) { - return System.getProperty(name.getLiterat()); - } + return template; } - final static class MissingProxyAuthenticationConfig extends RuntimeException { - MissingProxyAuthenticationConfig(ProxyConfigNames name) { - super(String.format("Expected System property '%s' not set!", name.getLiterat())); - } + boolean shouldConfigureProxySettings() { + return proxyConfig.isComplete(); } /** - * This interface abstracts the network side effect done by the underlying HTTP - */ - interface HttpRequester { - ImportScanResponse exchange(String endpoint, HttpEntity payload); - } - - /** - * Default implementation which utilizes {@link RestTemplate} + * Configuring proxy authentication explicitly + * + *

+ * This isn't done by default for spring rest templates.This method expects these four system properties (Java flag + * {@literal -DpropertyName}) to be set: + *

+ *
    + *
  • http.proxyUser
  • + *
  • http.proxyPassword
  • + *
  • http.proxyHost
  • + *
  • http.proxyPort
  • + *
+ * + * @return never {@code null} */ - static final class DefaultHttpRequester implements HttpRequester { - private final SystemPropertyFinder properties = new SystemPropertyFinder(); - - @Override - public ImportScanResponse exchange(final String url, final HttpEntity payload) { - final var restTemplate = this.createRestTemplate(); - return restTemplate.exchange( - url, - HttpMethod.POST, - payload, - ImportScanResponse.class) - .getBody(); - } - - private RestTemplate createRestTemplate() { - final var template = new RestTemplate(); - - if (shouldConfigureProxySettings()) { - template.setRequestFactory(createRequestFactoryWithProxyAuthConfig()); - } - - template.setMessageConverters(HTTP_MESSAGE_CONVERTERS); - - return template; - } - - boolean shouldConfigureProxySettings() { - return properties.hasProperty(ProxyConfigNames.HTTP_PROXY_USER) - && properties.hasProperty(ProxyConfigNames.HTTP_PROXY_PASSWORD); - } - - /** - * Configuring proxy authentication explicitly - * - *

- * This isn't done by default for spring rest templates.This method expects these four system properties (Java flag - * {@literal -DpropertyName}) to be set: - *

- *
    - *
  • http.proxyUser
  • - *
  • http.proxyPassword
  • - *
  • http.proxyHost
  • - *
  • http.proxyPort
  • - *
- * - * @return never {@code null} - */ - ClientHttpRequestFactory createRequestFactoryWithProxyAuthConfig() { - if (properties.notHasProperty(ProxyConfigNames.HTTP_PROXY_USER)) { - throw new MissingProxyAuthenticationConfig(ProxyConfigNames.HTTP_PROXY_USER); - } - - if (properties.notHasProperty(ProxyConfigNames.HTTP_PROXY_PASSWORD)) { - throw new MissingProxyAuthenticationConfig(ProxyConfigNames.HTTP_PROXY_PASSWORD); - } - - if (properties.notHasProperty(ProxyConfigNames.HTTP_PROXY_HOST)) { - throw new MissingProxyAuthenticationConfig(ProxyConfigNames.HTTP_PROXY_HOST); - } - - if (properties.notHasProperty(ProxyConfigNames.HTTP_PROXY_PORT)) { - throw new MissingProxyAuthenticationConfig(ProxyConfigNames.HTTP_PROXY_PORT); - } - - final var proxyHost = properties.getProperty(ProxyConfigNames.HTTP_PROXY_HOST); - final int proxyPort; - try { - proxyPort = Integer.parseInt(properties.getProperty(ProxyConfigNames.HTTP_PROXY_PORT)); - } catch (final NumberFormatException e) { - throw new IllegalArgumentException( - String.format("Given port for proxy authentication configuration (property '%s') is not a valid number! Given value wa '%s'.", - ProxyConfigNames.HTTP_PROXY_PORT.getLiterat(), - System.getProperty("http.proxyPort")), - e); - } - - final var credentials = new BasicCredentialsProvider(); - credentials.setCredentials( - new AuthScope(proxyHost, proxyPort), - new UsernamePasswordCredentials( - properties.getProperty(ProxyConfigNames.HTTP_PROXY_USER), - properties.getProperty(ProxyConfigNames.HTTP_PROXY_PASSWORD)) - ); - - final var clientBuilder = HttpClientBuilder.create(); - clientBuilder.useSystemProperties(); - clientBuilder.setProxy(new HttpHost(proxyHost, proxyPort)); - clientBuilder.setDefaultCredentialsProvider(credentials); - clientBuilder.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()); - - final var factory = new HttpComponentsClientHttpRequestFactory(); - factory.setHttpClient(clientBuilder.build()); - return factory; - } + ClientHttpRequestFactory createRequestFactoryWithProxyAuthConfig() { + final var credentials = new BasicCredentialsProvider(); + credentials.setCredentials( + new AuthScope(proxyConfig.getHost(), proxyConfig.getPort()), + new UsernamePasswordCredentials( + proxyConfig.getUser(), + proxyConfig.getPassword()) + ); + + final var clientBuilder = HttpClientBuilder.create(); + clientBuilder.useSystemProperties(); + clientBuilder.setProxy(new HttpHost(proxyConfig.getHost(), proxyConfig.getPort())); + clientBuilder.setDefaultCredentialsProvider(credentials); + clientBuilder.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()); + + final var factory = new HttpComponentsClientHttpRequestFactory(); + factory.setHttpClient(clientBuilder.build()); + return factory; } } diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/service/GenericDefectDojoService.java b/src/main/java/io/securecodebox/persistence/defectdojo/service/GenericDefectDojoService.java index 1858b851..084c0b64 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/service/GenericDefectDojoService.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/service/GenericDefectDojoService.java @@ -12,22 +12,15 @@ import com.fasterxml.jackson.databind.cfg.CoercionInputShape; import io.securecodebox.persistence.defectdojo.config.Config; import io.securecodebox.persistence.defectdojo.exception.LoopException; +import io.securecodebox.persistence.defectdojo.http.Foo; import io.securecodebox.persistence.defectdojo.model.BaseModel; -import io.securecodebox.persistence.defectdojo.model.Response; import io.securecodebox.persistence.defectdojo.model.Engagement; -import org.apache.http.HttpHost; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.impl.client.BasicCredentialsProvider; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.impl.client.ProxyAuthenticationStrategy; +import io.securecodebox.persistence.defectdojo.model.Response; +import lombok.Getter; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.http.converter.ResourceHttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; @@ -38,9 +31,7 @@ import java.net.URI; import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; import java.util.*; -import lombok.Getter; // FIXME: Should be package private bc implementation detail. abstract public class GenericDefectDojoService { @@ -75,46 +66,11 @@ public GenericDefectDojoService(Config config) { * @return The DefectDojo Authentication Header */ private HttpHeaders getDefectDojoAuthorizationHeaders() { - HttpHeaders headers = new HttpHeaders(); - headers.set("Authorization", "Token " + this.config.getApiKey()); - - String username = System.getProperty("http.proxyUser", ""); - String password = System.getProperty("http.proxyPassword", ""); - - if (!username.isEmpty() || !password.isEmpty()) { - System.out.println("Setting Proxy Auth Header..."); - headers.set(HttpHeaders.PROXY_AUTHORIZATION, "Basic " + Base64.getEncoder().encodeToString((username + ':' + password).getBytes(StandardCharsets.UTF_8))); - } - - return headers; + return new Foo(config).getDefectDojoAuthorizationHeaders(); } private RestTemplate setupRestTemplate() { - RestTemplate restTemplate; - - if (System.getProperty("http.proxyUser") != null && System.getProperty("http.proxyPassword") != null) { - // Configuring Proxy Authentication explicitly as it isn't done by default for spring rest templates :( - CredentialsProvider credsProvider = new BasicCredentialsProvider(); - credsProvider.setCredentials( - new AuthScope(System.getProperty("http.proxyHost"), Integer.parseInt(System.getProperty("http.proxyPort"))), - new UsernamePasswordCredentials(System.getProperty("http.proxyUser"), System.getProperty("http.proxyPassword")) - ); - HttpClientBuilder clientBuilder = HttpClientBuilder.create(); - - clientBuilder.useSystemProperties(); - clientBuilder.setProxy(new HttpHost(System.getProperty("http.proxyHost"), Integer.parseInt(System.getProperty("http.proxyPort")))); - clientBuilder.setDefaultCredentialsProvider(credsProvider); - clientBuilder.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()); - - CloseableHttpClient client = clientBuilder.build(); - - HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(); - factory.setHttpClient(client); - restTemplate = new RestTemplate(factory); - } else { - restTemplate = new RestTemplate(); - } - + RestTemplate restTemplate = new Foo(config).setupRestTemplate(); MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); converter.setObjectMapper(this.objectMapper); restTemplate.setMessageConverters(List.of( diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/service/ImportScanService.java b/src/main/java/io/securecodebox/persistence/defectdojo/service/ImportScanService.java index 437c32fc..808c2995 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/service/ImportScanService.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/service/ImportScanService.java @@ -7,9 +7,11 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.securecodebox.persistence.defectdojo.ScanType; import io.securecodebox.persistence.defectdojo.config.Config; +import io.securecodebox.persistence.defectdojo.http.ProxyConfig; +import io.securecodebox.persistence.defectdojo.http.ProxyConfigFactory; import io.securecodebox.persistence.defectdojo.model.ScanFile; import lombok.Data; -import lombok.Getter; +import lombok.NonNull; import java.util.HashMap; import java.util.Map; @@ -25,7 +27,18 @@ public interface ImportScanService { * @return never {@code null} */ static ImportScanService createDefault(final Config config) { - return new DefaultImportScanService(config); + return createDefault(config, new ProxyConfigFactory().create()); + } + + /** + * Factory method to create new instance of service default implementation + * + * @param config must not be {@code null} + * @param proxyConfig must not be {@code null} + * @return never {@code null} + */ + static ImportScanService createDefault(@NonNull final Config config, @NonNull final ProxyConfig proxyConfig) { + return new DefaultImportScanService(config, proxyConfig); } default ImportScanResponse importScan(ScanFile scanFile, long engagementId, long lead, String currentDate, ScanType scanType, long testType) { @@ -51,28 +64,4 @@ class ImportScanResponse { @JsonProperty("test") protected long testId; } - - /** - * These properties can be configured by passing them to the running Java process w/ flag {@literal -D} - *

- * Example: {@literal java -Dhttp.proxyHost=... -D... -jar ...} - *

- *

- * Important: All four parameters are mandatory. You must set them all - * or none of them! - *

- */ - enum ProxyConfigNames { - HTTP_PROXY_HOST("http.proxyHost"), - HTTP_PROXY_PORT("http.proxyPort"), - HTTP_PROXY_USER("http.proxyUser"), - HTTP_PROXY_PASSWORD("http.proxyPassword"); - - @Getter - private final String literat; - - ProxyConfigNames(String literat) { - this.literat = literat; - } - } } diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/service/ImportScanService2.java b/src/main/java/io/securecodebox/persistence/defectdojo/service/ImportScanService2.java new file mode 100644 index 00000000..36c660be --- /dev/null +++ b/src/main/java/io/securecodebox/persistence/defectdojo/service/ImportScanService2.java @@ -0,0 +1,143 @@ +// SPDX-FileCopyrightText: the secureCodeBox authors +// +// SPDX-License-Identifier: Apache-2.0 + +package io.securecodebox.persistence.defectdojo.service; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.securecodebox.persistence.defectdojo.ScanType; +import io.securecodebox.persistence.defectdojo.config.Config; +import io.securecodebox.persistence.defectdojo.exception.PersistenceException; +import io.securecodebox.persistence.defectdojo.http.Foo; +import io.securecodebox.persistence.defectdojo.model.ScanFile; +import lombok.Data; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.converter.FormHttpMessageConverter; +import org.springframework.http.converter.ResourceHttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + * Copied the version before I did refactoring for easier compare of duplicated code + */ +public class ImportScanService2 { + + private final Config config; + @Deprecated + protected String defectDojoUrl; + @Deprecated + protected String defectDojoApiKey; + + public ImportScanService2(Config config) { + super(); + this.config = config; + this.defectDojoUrl = config.getUrl(); + this.defectDojoApiKey = config.getApiKey(); + } + + /** + * @return The DefectDojo Authentication Header + */ + private HttpHeaders getDefectDojoAuthorizationHeaders() { + return new Foo(config).getDefectDojoAuthorizationHeaders(); + } + + protected RestTemplate setupRestTemplate() { + return new Foo(config).setupRestTemplate(); + } + + /** + * Before version 1.5.4. testName (in DefectDojo _test_type_) must be defectDojoScanName, afterwards, you can have somethings else + */ + protected ImportScanResponse createFindings(ScanFile scanFile, String endpoint, long lead, String currentDate, ScanType scanType, long testType, MultiValueMap options) { + var restTemplate = this.setupRestTemplate(); + HttpHeaders headers = getDefectDojoAuthorizationHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); + restTemplate.setMessageConverters(List.of( + new FormHttpMessageConverter(), + new ResourceHttpMessageConverter(), + new MappingJackson2HttpMessageConverter()) + ); + + MultiValueMap mvn = new LinkedMultiValueMap<>(); + + mvn.add("lead", Long.toString(lead)); + mvn.add("scan_date", currentDate); + mvn.add("scan_type", scanType.getTestType()); + mvn.add("close_old_findings", "true"); + mvn.add("skip_duplicates", "false"); + mvn.add("test_type", String.valueOf(testType)); + + for (String theKey : options.keySet()) { + mvn.remove(theKey); + } + mvn.addAll(options); + + try { + ByteArrayResource contentsAsResource = new ByteArrayResource(scanFile.getContent().getBytes(StandardCharsets.UTF_8)) { + @Override + public String getFilename() { + return scanFile.getName(); + } + }; + + mvn.add("file", contentsAsResource); + + var payload = new HttpEntity<>(mvn, headers); + + return restTemplate.exchange(defectDojoUrl + "/api/v2/" + endpoint + "/", HttpMethod.POST, payload, ImportScanResponse.class).getBody(); + } catch (HttpClientErrorException e) { + throw new PersistenceException("Failed to attach findings to engagement."); + } + } + + + public ImportScanResponse importScan(ScanFile scanFile, long engagementId, long lead, String currentDate, ScanType scanType, long testType) { + var additionalValues = new LinkedMultiValueMap(); + additionalValues.add("engagement", Long.toString(engagementId)); + + return this.importScan(scanFile, engagementId, lead, currentDate, scanType, testType, additionalValues); + } + + public ImportScanResponse reimportScan(ScanFile scanFile, long testId, long lead, String currentDate, ScanType scanType, long testType) { + var additionalValues = new LinkedMultiValueMap(); + additionalValues.add("test", Long.toString(testId)); + + return this.reimportScan(scanFile, testId, lead, currentDate, scanType, testType, additionalValues); + } + + //overloading with optional parameter + public ImportScanResponse importScan(ScanFile scanFile, long engagementId, long lead, String currentDate, ScanType scanType, long testType, LinkedMultiValueMap additionalValues) { + additionalValues.add("engagement", Long.toString(engagementId)); + + return this.createFindings(scanFile, "import-scan", lead, currentDate, scanType, testType, additionalValues); + } + + public ImportScanResponse reimportScan(ScanFile scanFile, long testId, long lead, String currentDate, ScanType scanType, long testType, LinkedMultiValueMap additionalValues) { + additionalValues.add("test", Long.toString(testId)); + + return this.createFindings(scanFile, "reimport-scan", lead, currentDate, scanType, testType, additionalValues); + } + + @Data + public static class ImportScanResponse { + @JsonProperty + protected Boolean verified; + + @JsonProperty + protected Boolean active; + + @JsonProperty("test") + protected long testId; + } +} diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/http/MissingProxyConfigValueTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/http/MissingProxyConfigValueTest.java new file mode 100644 index 00000000..75613bc5 --- /dev/null +++ b/src/test/java/io/securecodebox/persistence/defectdojo/http/MissingProxyConfigValueTest.java @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: the secureCodeBox authors +// +// SPDX-License-Identifier: Apache-2.0 + +package io.securecodebox.persistence.defectdojo.http; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +/** + * Tests for {@link MissingProxyConfigValue} + */ +class MissingProxyConfigValueTest { + @Test + void rendersMessageFromProxyConfigName() { + final var sut = new MissingProxyConfigValue(ProxyConfigNames.HTTP_PROXY_HOST); + + assertThat(sut.getMessage(), is("Expected system property 'http.proxyHost' not set!")); + } +} diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/http/ProxyConfigFactoryTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/http/ProxyConfigFactoryTest.java new file mode 100644 index 00000000..59d6e228 --- /dev/null +++ b/src/test/java/io/securecodebox/persistence/defectdojo/http/ProxyConfigFactoryTest.java @@ -0,0 +1,97 @@ +// SPDX-FileCopyrightText: the secureCodeBox authors +// +// SPDX-License-Identifier: Apache-2.0 + +package io.securecodebox.persistence.defectdojo.http; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import uk.org.webcompere.systemstubs.jupiter.SystemStub; +import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; +import uk.org.webcompere.systemstubs.properties.SystemProperties; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Tests for {@link ProxyConfigFactory} + */ +@ExtendWith(SystemStubsExtension.class) +class ProxyConfigFactoryTest { + @SystemStub + private SystemProperties restoreSystemProperties; + private final ProxyConfigFactory sut = new ProxyConfigFactory(); + + @Test + void create_throesExceptionIfUserNotSet() { + System.clearProperty(ProxyConfigNames.HTTP_PROXY_USER.getLiterat()); + System.setProperty(ProxyConfigNames.HTTP_PROXY_PASSWORD.getLiterat(), "password"); + System.setProperty(ProxyConfigNames.HTTP_PROXY_HOST.getLiterat(), "host"); + System.setProperty(ProxyConfigNames.HTTP_PROXY_PORT.getLiterat(), "4242"); + + final var thrown = assertThrows( + MissingProxyConfigValue.class, + sut::create); + + assertThat(thrown.getMessage(), containsString("'http.proxyUser'")); + } + + @Test + void create_throesExceptionIfPasswordNotSet() { + System.setProperty(ProxyConfigNames.HTTP_PROXY_USER.getLiterat(), "user"); + System.clearProperty(ProxyConfigNames.HTTP_PROXY_PASSWORD.getLiterat()); + System.setProperty(ProxyConfigNames.HTTP_PROXY_HOST.getLiterat(), "host"); + System.setProperty(ProxyConfigNames.HTTP_PROXY_PORT.getLiterat(), "4242"); + + final var thrown = assertThrows( + MissingProxyConfigValue.class, + sut::create); + + assertThat(thrown.getMessage(), containsString("'http.proxyPassword'")); + } + + @Test + void create_throesExceptionIfHostNotSet() { + System.setProperty(ProxyConfigNames.HTTP_PROXY_USER.getLiterat(), "user"); + System.setProperty(ProxyConfigNames.HTTP_PROXY_PASSWORD.getLiterat(), "password"); + System.clearProperty(ProxyConfigNames.HTTP_PROXY_HOST.getLiterat()); + System.setProperty(ProxyConfigNames.HTTP_PROXY_PORT.getLiterat(), "4242"); + + final var thrown = assertThrows( + MissingProxyConfigValue.class, + sut::create); + + assertThat(thrown.getMessage(), containsString("'http.proxyHost'")); + } + + @Test + void create_throesExceptionIfPortNotSet() { + System.setProperty(ProxyConfigNames.HTTP_PROXY_USER.getLiterat(), "user"); + System.setProperty(ProxyConfigNames.HTTP_PROXY_PASSWORD.getLiterat(), "password"); + System.setProperty(ProxyConfigNames.HTTP_PROXY_HOST.getLiterat(), "host"); + System.clearProperty(ProxyConfigNames.HTTP_PROXY_PORT.getLiterat()); + + final var thrown = assertThrows( + MissingProxyConfigValue.class, + sut::create); + + assertThat(thrown.getMessage(), containsString("'http.proxyPort'")); + } + + @Test + void create_throesExceptionIfPortIsNotInteger() { + System.setProperty(ProxyConfigNames.HTTP_PROXY_USER.getLiterat(), "user"); + System.setProperty(ProxyConfigNames.HTTP_PROXY_PASSWORD.getLiterat(), "password"); + System.setProperty(ProxyConfigNames.HTTP_PROXY_HOST.getLiterat(), "host"); + System.setProperty(ProxyConfigNames.HTTP_PROXY_PORT.getLiterat(), "FUBAR"); + + final var thrown = assertThrows( + IllegalArgumentException.class, + sut::create); + + assertThat( + thrown.getMessage(), + is("Given port for proxy authentication configuration (property 'http.proxyPort') is not a valid number! Given value wa 'FUBAR'.")); + } +} diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/http/ProxyConfigTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/http/ProxyConfigTest.java new file mode 100644 index 00000000..bb93248f --- /dev/null +++ b/src/test/java/io/securecodebox/persistence/defectdojo/http/ProxyConfigTest.java @@ -0,0 +1,87 @@ +// SPDX-FileCopyrightText: the secureCodeBox authors +// +// SPDX-License-Identifier: Apache-2.0 + +package io.securecodebox.persistence.defectdojo.http; + +import nl.jqno.equalsverifier.EqualsVerifier; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +/** + * Tests for {@link ProxyConfig} + */ +class ProxyConfigTest { + @Test + void equalsAndHashCode() { + EqualsVerifier.forClass(ProxyConfig.class).verify(); + } + + @Test + void builderCreatesDefault() { + final var sut = ProxyConfig.builder().build(); + + assertAll( + () -> assertThat(sut.getUser(), is(emptyString())), + () -> assertThat(sut.getPassword(), is(emptyString())), + () -> assertThat(sut.getHost(), is(emptyString())), + () -> assertThat(sut.getPort(), is(0)) + ); + } + + @Test + void buildersDefaultIsEqualToNullObject() { + assertThat(ProxyConfig.builder().build(), is(ProxyConfig.NULL)); + } + + @Test + void isComplete_falseForNullObject() { + assertThat(ProxyConfig.NULL.isComplete(), is(false)); + } + + @Test + void isComplete_falseForDefault() { + assertThat(ProxyConfig.builder().build().isComplete(), is(false)); + } + + @ParameterizedTest + @MethodSource("incompleteConfigs") + void isComplete_falseUnlessAllFieldsAreSet(final ProxyConfig sut) { + + } + + private static Stream incompleteConfigs() { + return Stream.of( + // Only one is set: + Arguments.of(ProxyConfig.builder().user("user").build()), + Arguments.of(ProxyConfig.builder().password("pw").build()), + Arguments.of(ProxyConfig.builder().host("host").build()), + Arguments.of(ProxyConfig.builder().port(42).build()), + // All but one is set: + Arguments.of(ProxyConfig.builder().password("pwd").host("host").port(42).build()), + Arguments.of(ProxyConfig.builder().user("user").host("host").port(42).build()), + Arguments.of(ProxyConfig.builder().user("user").password("pwd").port(42).build()), + Arguments.of(ProxyConfig.builder().user("user").password("pwd").host("host").build()) + ); + } + + @Test + void isComplete_trueIfAllFieldsAreNonDefaults() { + final var sut = ProxyConfig.builder() + .user("user") + .password("pw") + .host("host") + .port(42) + .build(); + + assertThat(sut.isComplete(), is(true)); + } +} diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/service/DefaultHttpRequesterTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/service/DefaultHttpRequesterTest.java deleted file mode 100644 index 84be4edb..00000000 --- a/src/test/java/io/securecodebox/persistence/defectdojo/service/DefaultHttpRequesterTest.java +++ /dev/null @@ -1,137 +0,0 @@ -// SPDX-FileCopyrightText: the secureCodeBox authors -// -// SPDX-License-Identifier: Apache-2.0 - -package io.securecodebox.persistence.defectdojo.service; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import uk.org.webcompere.systemstubs.jupiter.SystemStub; -import uk.org.webcompere.systemstubs.jupiter.SystemStubsExtension; -import uk.org.webcompere.systemstubs.properties.SystemProperties; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertThrows; - -/** - * Tests for {@link DefaultHttpRequesterTest} - */ -@ExtendWith(SystemStubsExtension.class) -final class DefaultHttpRequesterTest { - @SystemStub - - private SystemProperties systemProperties; - private final DefaultImportScanService.DefaultHttpRequester sut = new DefaultImportScanService.DefaultHttpRequester(); - - @Test - void shouldConfigureProxySettings_falseIfNeitherUserNorPasswordIsSet() { - assertThat(sut.shouldConfigureProxySettings(), is(false)); - } - - @Test - void shouldConfigureProxySettings_falseIfUserSetButPasswordNot() throws Exception { - systemProperties.set(ImportScanService.ProxyConfigNames.HTTP_PROXY_USER.getLiterat(), "user"); - - systemProperties.execute(() -> { - assertThat(sut.shouldConfigureProxySettings(), is(false)); - }); - } - - @Test - void shouldConfigureProxySettings_falseIfPasswordSetButUserNot() throws Exception { - systemProperties.set(ImportScanService.ProxyConfigNames.HTTP_PROXY_PASSWORD.getLiterat(), "password"); - - systemProperties.execute(() -> { - assertThat(sut.shouldConfigureProxySettings(), is(false)); - }); - } - - @Test - void shouldConfigureProxySettings_trueIfUserAndPasswordSet() throws Exception { - systemProperties.set(ImportScanService.ProxyConfigNames.HTTP_PROXY_USER.getLiterat(), "user"); - systemProperties.set(ImportScanService.ProxyConfigNames.HTTP_PROXY_PASSWORD.getLiterat(), "password"); - - systemProperties.execute(() -> { - assertThat(sut.shouldConfigureProxySettings(), is(true)); - }); - } - - @Test - void createRequestFactoryWithProxyAuthConfig_throesExceptionIfUserNotSet() throws Exception { - systemProperties.set(ImportScanService.ProxyConfigNames.HTTP_PROXY_PASSWORD.getLiterat(), "password"); - systemProperties.set(ImportScanService.ProxyConfigNames.HTTP_PROXY_HOST.getLiterat(), "host"); - systemProperties.set(ImportScanService.ProxyConfigNames.HTTP_PROXY_PORT.getLiterat(), "4242"); - - systemProperties.execute(() -> { - final var thrown = assertThrows( - DefaultImportScanService.MissingProxyAuthenticationConfig.class, - sut::createRequestFactoryWithProxyAuthConfig); - - assertThat(thrown.getMessage(), is("Expected System property 'http.proxyUser' not set!")); - }); - } - - @Test - void createRequestFactoryWithProxyAuthConfig_throesExceptionIfPasswordNotSet() throws Exception { - systemProperties.set(ImportScanService.ProxyConfigNames.HTTP_PROXY_USER.getLiterat(), "user"); - systemProperties.set(ImportScanService.ProxyConfigNames.HTTP_PROXY_HOST.getLiterat(), "host"); - systemProperties.set(ImportScanService.ProxyConfigNames.HTTP_PROXY_PORT.getLiterat(), "4242"); - - systemProperties.execute(() -> { - final var thrown = assertThrows( - DefaultImportScanService.MissingProxyAuthenticationConfig.class, - sut::createRequestFactoryWithProxyAuthConfig); - - assertThat(thrown.getMessage(), is("Expected System property 'http.proxyPassword' not set!")); - }); - } - - @Test - void createRequestFactoryWithProxyAuthConfig_throesExceptionIfHostNotSet() throws Exception { - systemProperties.set(ImportScanService.ProxyConfigNames.HTTP_PROXY_USER.getLiterat(), "user"); - systemProperties.set(ImportScanService.ProxyConfigNames.HTTP_PROXY_PASSWORD.getLiterat(), "password"); - systemProperties.set(ImportScanService.ProxyConfigNames.HTTP_PROXY_PORT.getLiterat(), "4242"); - - systemProperties.execute(() -> { - final var thrown = assertThrows( - DefaultImportScanService.MissingProxyAuthenticationConfig.class, - sut::createRequestFactoryWithProxyAuthConfig); - - assertThat(thrown.getMessage(), is("Expected System property 'http.proxyHost' not set!")); - }); - } - - @Test - void createRequestFactoryWithProxyAuthConfig_throesExceptionIfPortNotSet() throws Exception { - systemProperties.set(ImportScanService.ProxyConfigNames.HTTP_PROXY_USER.getLiterat(), "user"); - systemProperties.set(ImportScanService.ProxyConfigNames.HTTP_PROXY_PASSWORD.getLiterat(), "password"); - systemProperties.set(ImportScanService.ProxyConfigNames.HTTP_PROXY_HOST.getLiterat(), "host"); - - systemProperties.execute(() -> { - final var thrown = assertThrows( - DefaultImportScanService.MissingProxyAuthenticationConfig.class, - sut::createRequestFactoryWithProxyAuthConfig); - - assertThat(thrown.getMessage(), is("Expected System property 'http.proxyPort' not set!")); - }); - } - - @Test - void createRequestFactoryWithProxyAuthConfig_throesExceptionIfPortIsNotInteger() throws Exception { - systemProperties.set(ImportScanService.ProxyConfigNames.HTTP_PROXY_USER.getLiterat(), "user"); - systemProperties.set(ImportScanService.ProxyConfigNames.HTTP_PROXY_PASSWORD.getLiterat(), "password"); - systemProperties.set(ImportScanService.ProxyConfigNames.HTTP_PROXY_HOST.getLiterat(), "host"); - systemProperties.set(ImportScanService.ProxyConfigNames.HTTP_PROXY_PORT.getLiterat(), "FUBAR"); - - systemProperties.execute(() -> { - final var thrown = assertThrows( - IllegalArgumentException.class, - sut::createRequestFactoryWithProxyAuthConfig); - - assertThat( - thrown.getMessage(), - is("Given port for proxy authentication configuration (property 'http.proxyPort') is not a valid number! Given value wa 'FUBAR'.")); - }); - } -} diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/service/DefaultImportScanServiceTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/service/DefaultImportScanServiceTest.java index 538eaff7..902e4d22 100644 --- a/src/test/java/io/securecodebox/persistence/defectdojo/service/DefaultImportScanServiceTest.java +++ b/src/test/java/io/securecodebox/persistence/defectdojo/service/DefaultImportScanServiceTest.java @@ -5,13 +5,15 @@ package io.securecodebox.persistence.defectdojo.service; import io.securecodebox.persistence.defectdojo.config.Config; +import io.securecodebox.persistence.defectdojo.http.ProxyConfig; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertThrows; /** * Tests for {@link DefaultImportScanService} @@ -22,25 +24,53 @@ class DefaultImportScanServiceTest { "apiKey", 23 ); - private final DefaultImportScanService sut = new DefaultImportScanService(config); + private final DefaultImportScanService sut = new DefaultImportScanService(config, ProxyConfig.NULL); @Test void constructorShouldThrowExceptionOnNullConfig() { assertThrows(NullPointerException.class, () -> { - new DefaultImportScanService(null); + new DefaultImportScanService(null, ProxyConfig.NULL); }); } @Test - void createDefectDojoAuthorizationHeaders_apiKeyFromConfigShouldBePresentAsAuthHeader() { - final var authorizationHeaders = sut.createDefectDojoAuthorizationHeaders(); + void constructorShouldThrowExceptionOnNullProxyConfig() { + assertThrows(NullPointerException.class, () -> { + new DefaultImportScanService(Config.NULL, null); + }); + } + @Test + void createDefectDojoAuthorizationHeaders_apiKeyFromConfigShouldBePresentAsAuthHEader() { + final var authorizationHeaders = sut.createDefectDojoAuthorizationHeaders(); assertAll( () -> assertThat(authorizationHeaders.size(), is(1)), () -> assertThat(authorizationHeaders.get(HttpHeaders.AUTHORIZATION).get(0), is("Token apiKey")) ); } + @Test + void shouldConfigureProxySettings_trueIfProxyConfigIsComplete() { + final var proxyConfig = ProxyConfig.builder() + .user("user") + .password("pw") + .host("host") + .port(42) + .build(); + final var innerSut = new DefaultImportScanService(config, proxyConfig); + + assertThat(innerSut.shouldConfigureProxySettings(), is(true)); + } + + @Test + void shouldConfigureProxySettings_falseIfProxyConfigIsIncomplete() { + final var proxyConfig = ProxyConfig.builder() + .build(); + final var innerSut = new DefaultImportScanService(config, proxyConfig); + + assertThat(innerSut.shouldConfigureProxySettings(), is(false)); + } + @Test void generateApiUrl() { assertThat(sut.generateApiUrl("foo"), is("http://localhost/api/v2/foo/")); diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/service/ImportScanServiceTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/service/ImportScanServiceTest.java index 6263cf7c..d49f1ce6 100644 --- a/src/test/java/io/securecodebox/persistence/defectdojo/service/ImportScanServiceTest.java +++ b/src/test/java/io/securecodebox/persistence/defectdojo/service/ImportScanServiceTest.java @@ -6,6 +6,7 @@ import io.securecodebox.persistence.defectdojo.ScanType; import io.securecodebox.persistence.defectdojo.config.Config; +import io.securecodebox.persistence.defectdojo.http.ProxyConfig; import io.securecodebox.persistence.defectdojo.model.ScanFile; import lombok.Getter; import org.junit.jupiter.api.Test; @@ -25,9 +26,16 @@ class ImportScanServiceTest { private final ImportScanServiceStub sut = new ImportScanServiceStub(); @Test - void createDefault_throwsExceptionIfNullPassedIn() { + void createDefault_throwsExceptionIfNullPassedInAsConfig() { assertThrows(NullPointerException.class, () -> { - ImportScanService.createDefault(null); + ImportScanService.createDefault(null, ProxyConfig.NULL); + }); + } + + @Test + void createDefault_throwsExceptionIfNullPassedInAsProxyConfig() { + assertThrows(NullPointerException.class, () -> { + ImportScanService.createDefault(Config.NULL, null); }); } @@ -39,7 +47,7 @@ void createDefault_passesConfig() { 23 ); - final var sut = (DefaultImportScanService) ImportScanService.createDefault(config); + final var sut = (DefaultImportScanService) ImportScanService.createDefault(config, ProxyConfig.NULL); assertAll( () -> assertThat(sut.getDefectDojoUrl(), is(config.getUrl())),