diff --git a/.sdkmanrc b/.sdkmanrc index b0539704..318d1af5 100644 --- a/.sdkmanrc +++ b/.sdkmanrc @@ -4,4 +4,4 @@ # Enable auto-env through the sdkman_auto_env config # Add key=value pairs of SDKs to use below -java=17.0.7-tem +java=17.0.10-tem diff --git a/README.md b/README.md index 9b59f365..cafab4d9 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ The client is supposed to be compatible with DefectDojo 1.10 and later, older ve ```java import com.fasterxml.jackson.core.JsonProcessingException; +import io.securecodebox.persistence.defectdojo.config.ClientConfig; import io.securecodebox.persistence.defectdojo.config.Config; import io.securecodebox.persistence.defectdojo.service.ProductTypeService; @@ -41,7 +42,7 @@ public class DefectDojoClientTest { public static void main(String[] args) throws URISyntaxException, JsonProcessingException { // Configure DefectDojo URl and APIv2 Key - var conf = new Config("https://defectdojo.example.com", "f8...."); + var conf = new ClientConfig("https://defectdojo.example.com", "f8...."); var productTypeService = new ProductTypeService(conf); var productTypes = productTypeService.search(); diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/config/Config.java b/src/main/java/io/securecodebox/persistence/defectdojo/config/ClientConfig.java similarity index 78% rename from src/main/java/io/securecodebox/persistence/defectdojo/config/Config.java rename to src/main/java/io/securecodebox/persistence/defectdojo/config/ClientConfig.java index 8f1b20dc..defed2a0 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/config/Config.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/config/ClientConfig.java @@ -4,7 +4,7 @@ package io.securecodebox.persistence.defectdojo.config; -import io.securecodebox.persistence.defectdojo.exception.ConfigException; +import io.securecodebox.persistence.defectdojo.exception.ClientConfigException; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NonNull; @@ -16,7 +16,11 @@ @Getter @ToString @EqualsAndHashCode -public final class Config { +public final class ClientConfig { + /** + * Path prefix for the Defectdojo REST API + */ + public static final String API_PREFIX = "/api/v2/"; /** * Default for {@link #maxPageCountForGets} */ @@ -24,7 +28,7 @@ public final class Config { /** * Null pattern object. */ - public static final Config NULL = new Config("", "", DEFAULT_MAX_PAGE_COUNT_FOR_GETS); + public static final ClientConfig NULL = new ClientConfig("", "", DEFAULT_MAX_PAGE_COUNT_FOR_GETS); /** * URL of the host which serves the DefectDojo API. @@ -59,7 +63,7 @@ public final class Config { * @param url not {@code null} * @param apiKey not {@code null} */ - public Config(final @NonNull String url, final @NonNull String apiKey) { + public ClientConfig(final @NonNull String url, final @NonNull String apiKey) { this(url, apiKey, DEFAULT_MAX_PAGE_COUNT_FOR_GETS); } @@ -70,7 +74,7 @@ public Config(final @NonNull String url, final @NonNull String apiKey) { * @param apiKey not {@code null} * @param maxPageCountForGets not less than 1 */ - public Config(final @NonNull String url, final @NonNull String apiKey, final int maxPageCountForGets) { + public ClientConfig(final @NonNull String url, final @NonNull String apiKey, final int maxPageCountForGets) { super(); this.url = url; this.apiKey = apiKey; @@ -90,7 +94,7 @@ private static int validateIsGreaterZero(final int number, final String name) { * * @return never {@code null} */ - public static Config fromEnv() { + public static ClientConfig fromEnv() { final var url = findRequiredEnvVar(EnvVars.DEFECTDOJO_URL); final var apiKey = findRequiredEnvVar(EnvVars.DEFECTDOJO_APIKEY); final int maxPageCountForGets; @@ -99,14 +103,14 @@ public static Config fromEnv() { try { maxPageCountForGets = Integer.parseInt(findEnvVar(EnvVars.DEFECTDOJO_MAX_PAGE_COUNT_FOR_GETS)); } catch (final NumberFormatException e) { - throw new ConfigException(String.format("Given value for environment variable '%s' is not a valid number! Given was '%s'.", EnvVars.DEFECTDOJO_MAX_PAGE_COUNT_FOR_GETS.literal, findEnvVar(EnvVars.DEFECTDOJO_MAX_PAGE_COUNT_FOR_GETS)), + throw new ClientConfigException(String.format("Given value for environment variable '%s' is not a valid number! Given was '%s'.", EnvVars.DEFECTDOJO_MAX_PAGE_COUNT_FOR_GETS.literal, findEnvVar(EnvVars.DEFECTDOJO_MAX_PAGE_COUNT_FOR_GETS)), e); } } else { maxPageCountForGets = DEFAULT_MAX_PAGE_COUNT_FOR_GETS; } - return new Config(url, apiKey, maxPageCountForGets); + return new ClientConfig(url, apiKey, maxPageCountForGets); } private static boolean hasEnvVar(final @NonNull EnvVars name) { @@ -121,7 +125,7 @@ private static String findRequiredEnvVar(final @NonNull EnvVars name) { final var envVar = System.getenv(name.literal); if (envVar == null) { - throw new ConfigException(String.format("Missing environment variable '%s'!", name.literal)); + throw new ClientConfigException(String.format("Missing environment variable '%s'!", name.literal)); } return envVar; } diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/exception/ClientConfigException.java b/src/main/java/io/securecodebox/persistence/defectdojo/exception/ClientConfigException.java new file mode 100644 index 00000000..cb055211 --- /dev/null +++ b/src/main/java/io/securecodebox/persistence/defectdojo/exception/ClientConfigException.java @@ -0,0 +1,21 @@ +// Copyright 2021 iteratec GmbH +// SPDX-FileCopyrightText: 2023 iteratec GmbH +// +// SPDX-License-Identifier: Apache-2.0 + +package io.securecodebox.persistence.defectdojo.exception; + +import io.securecodebox.persistence.defectdojo.config.ClientConfig; + +/** + * Indicates a missing/bad {@link ClientConfig config property} + */ +public final class ClientConfigException extends PersistenceException { + public ClientConfigException(final String message) { + super(message); + } + + public ClientConfigException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/exception/ConfigException.java b/src/main/java/io/securecodebox/persistence/defectdojo/exception/ConfigException.java deleted file mode 100644 index 38b159ba..00000000 --- a/src/main/java/io/securecodebox/persistence/defectdojo/exception/ConfigException.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2021 iteratec GmbH -// SPDX-FileCopyrightText: 2023 iteratec GmbH -// -// SPDX-License-Identifier: Apache-2.0 - -package io.securecodebox.persistence.defectdojo.exception; - -/** - * Indicates a missing {@link io.securecodebox.persistence.defectdojo.config.Config config property} - */ -public final class ConfigException extends RuntimeException { - public ConfigException(final String message) { - super(message); - } - - public ConfigException(final String message, final Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/exception/LoopException.java b/src/main/java/io/securecodebox/persistence/defectdojo/exception/LoopException.java deleted file mode 100644 index 5bebd374..00000000 --- a/src/main/java/io/securecodebox/persistence/defectdojo/exception/LoopException.java +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-FileCopyrightText: the secureCodeBox authors -// -// SPDX-License-Identifier: Apache-2.0 - -package io.securecodebox.persistence.defectdojo.exception; - -public final class LoopException extends RuntimeException { - public LoopException(String message) { - super(message); - } - - public LoopException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/exception/PersistenceException.java b/src/main/java/io/securecodebox/persistence/defectdojo/exception/PersistenceException.java index b480c094..e890c4c0 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/exception/PersistenceException.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/exception/PersistenceException.java @@ -4,7 +4,10 @@ package io.securecodebox.persistence.defectdojo.exception; -public final class PersistenceException extends RuntimeException { +/** + * Generic exception which is thrown for any unforeseen error at runtime + */ +public class PersistenceException extends RuntimeException { public PersistenceException(String message) { super(message); } diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/exception/TooManyResponsesException.java b/src/main/java/io/securecodebox/persistence/defectdojo/exception/TooManyResponsesException.java new file mode 100644 index 00000000..6af9e238 --- /dev/null +++ b/src/main/java/io/securecodebox/persistence/defectdojo/exception/TooManyResponsesException.java @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: the secureCodeBox authors +// +// SPDX-License-Identifier: Apache-2.0 + +package io.securecodebox.persistence.defectdojo.exception; + +import io.securecodebox.persistence.defectdojo.config.ClientConfig; + +/** + * Thrown if we receive more objects than {@link ClientConfig configured} + */ +public final class TooManyResponsesException extends PersistenceException { + public TooManyResponsesException(String message) { + super(message); + } +} diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/http/AuthHeaderFactory.java b/src/main/java/io/securecodebox/persistence/defectdojo/http/AuthHeaderFactory.java new file mode 100644 index 00000000..90c5a323 --- /dev/null +++ b/src/main/java/io/securecodebox/persistence/defectdojo/http/AuthHeaderFactory.java @@ -0,0 +1,50 @@ +package io.securecodebox.persistence.defectdojo.http; + +import io.securecodebox.persistence.defectdojo.config.ClientConfig; +import lombok.NonNull; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpHeaders; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +/** + * Utility class to create HTTP authorization headers + */ +@Slf4j +public final class AuthHeaderFactory { + private final ClientConfig clientConfig; + @Setter + @NonNull + private ProxyConfig proxyConfig = ProxyConfig.NULL; + + public AuthHeaderFactory(@NonNull ClientConfig clientConfig) { + super(); + this.clientConfig = clientConfig; + } + + /** + * This method generates appropriate authorization headers + * + * @return never {@code null} + */ + public HttpHeaders generateAuthorizationHeaders() { + final var headers = new HttpHeaders(); + log.debug("Add Authorization header."); + headers.set(HttpHeaders.AUTHORIZATION, "Token " + this.clientConfig.getApiKey()); + + if (proxyConfig.isComplete()) { + log.debug("Add Proxy-Authorization header."); + headers.set(HttpHeaders.PROXY_AUTHORIZATION, "Basic " + encodeProxyCredentials(proxyConfig)); + } + + return headers; + } + + String encodeProxyCredentials(@NonNull final ProxyConfig cfg) { + final var credential = String.format("%s:%s", cfg.getUser(), cfg.getPassword()); + return Base64.getEncoder().encodeToString(credential.getBytes(StandardCharsets.UTF_8)); + } + +} diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/http/Foo.java b/src/main/java/io/securecodebox/persistence/defectdojo/http/Foo.java deleted file mode 100644 index a44b06c9..00000000 --- a/src/main/java/io/securecodebox/persistence/defectdojo/http/Foo.java +++ /dev/null @@ -1,96 +0,0 @@ -// 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 lombok.extern.slf4j.Slf4j; -import org.apache.http.HttpHost; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.Credentials; -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.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 - */ -@Slf4j -public final class Foo { - private final Config config; - private final ProxyConfig proxyConfig; - - public Foo(@NonNull final Config config, @NonNull final ProxyConfig proxyConfig) { - super(); - this.config = config; - this.proxyConfig = proxyConfig; - } - - /** - * This method generates appropriate authorization headers - * - * @return never {@code null} - */ - public HttpHeaders generateAuthorizationHeaders() { - HttpHeaders headers = new HttpHeaders(); - headers.set(HttpHeaders.AUTHORIZATION, "Token " + this.config.getApiKey()); - - if (proxyConfig.isComplete()) { - log.info("Setting Proxy Auth Header..."); - headers.set(HttpHeaders.PROXY_AUTHORIZATION, "Basic " + encodeProxyCredentials(proxyConfig)); - } - - return headers; - } - - static String encodeProxyCredentials(@NonNull final ProxyConfig cfg) { - final var credential = String.format("%s:%s", cfg.getUser(), cfg.getPassword()); - return Base64.getEncoder().encodeToString(credential.getBytes(StandardCharsets.UTF_8)); - } - - public RestTemplate createRestTemplate() { - if (proxyConfig.isComplete()) { - // Configuring Proxy Authentication explicitly as it isn't done by default for spring rest templates :( - final var builder = HttpClientBuilder.create() - .useSystemProperties() - .setProxy(createHttpHost()) - .setDefaultCredentialsProvider(createCredentialsProvider()) - .setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()); - - final var factory = new HttpComponentsClientHttpRequestFactory(); - factory.setHttpClient(builder.build()); - - return new RestTemplate(factory); - } - - return new RestTemplate(); - } - - CredentialsProvider createCredentialsProvider() { - final var provider = new BasicCredentialsProvider(); - provider.setCredentials(createAuthScope(), createCredentials()); - return provider; - } - - AuthScope createAuthScope() { - return new AuthScope(proxyConfig.getHost(), proxyConfig.getPort()); - } - - Credentials createCredentials() { - return new UsernamePasswordCredentials(proxyConfig.getUser(), proxyConfig.getPassword()); - } - - HttpHost createHttpHost() { - return new HttpHost(proxyConfig.getHost(), proxyConfig.getPort()); - } -} diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/http/ProxyCredentialFactory.java b/src/main/java/io/securecodebox/persistence/defectdojo/http/ProxyCredentialFactory.java new file mode 100644 index 00000000..b927dedb --- /dev/null +++ b/src/main/java/io/securecodebox/persistence/defectdojo/http/ProxyCredentialFactory.java @@ -0,0 +1,34 @@ +package io.securecodebox.persistence.defectdojo.http; + +import lombok.NonNull; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.Credentials; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.impl.client.BasicCredentialsProvider; + +/** + * Utility class to create credentials to authenticate against a HTTP proxy + */ +final class ProxyCredentialFactory { + private final ProxyConfig proxyConfig; + + ProxyCredentialFactory(@NonNull ProxyConfig proxyConfig) { + super(); + this.proxyConfig = proxyConfig; + } + + CredentialsProvider createCredentialsProvider() { + final var provider = new BasicCredentialsProvider(); + provider.setCredentials(createAuthScope(), createCredentials()); + return provider; + } + + AuthScope createAuthScope() { + return new AuthScope(proxyConfig.getHost(), proxyConfig.getPort()); + } + + Credentials createCredentials() { + return new UsernamePasswordCredentials(proxyConfig.getUser(), proxyConfig.getPassword()); + } +} diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/http/RestTemplateFactory.java b/src/main/java/io/securecodebox/persistence/defectdojo/http/RestTemplateFactory.java new file mode 100644 index 00000000..1b0f7f71 --- /dev/null +++ b/src/main/java/io/securecodebox/persistence/defectdojo/http/RestTemplateFactory.java @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: the secureCodeBox authors +// +// SPDX-License-Identifier: Apache-2.0 + +package io.securecodebox.persistence.defectdojo.http; + +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.HttpHost; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.ProxyAuthenticationStrategy; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +/** + * Factory to create preconfigured {@link RestTemplate} + */ +@Slf4j +public final class RestTemplateFactory { + private final ProxyConfig proxyConfig; + + public RestTemplateFactory(@NonNull ProxyConfig proxyConfig) { + super(); + this.proxyConfig = proxyConfig; + } + + public RestTemplate createRestTemplate() { + if (proxyConfig.isComplete()) { + log.debug("Configure proxy authentication for REST template."); + // Configuring Proxy Authentication explicitly as it isn't done by default for spring rest templates :( + final var builder = HttpClientBuilder.create() + .useSystemProperties() + .setProxy(createHttpHost()) + .setDefaultCredentialsProvider(new ProxyCredentialFactory(proxyConfig).createCredentialsProvider()) + .setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()); + + final var factory = new HttpComponentsClientHttpRequestFactory(); + factory.setHttpClient(builder.build()); + + return new RestTemplate(factory); + } + + return new RestTemplate(); + } + + HttpHost createHttpHost() { + return new HttpHost(proxyConfig.getHost(), proxyConfig.getPort()); + } +} diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/model/Endpoint.java b/src/main/java/io/securecodebox/persistence/defectdojo/model/Endpoint.java index d1b31085..556cc649 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/model/Endpoint.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/model/Endpoint.java @@ -26,7 +26,7 @@ public final class Endpoint implements Model, HasId { @JsonProperty private String host; - @JsonProperty("fqdm") + @JsonProperty("fqdm") // FIXME: This seems to be a type, should be fqdn. private String fullyQualifiedDomainName; @JsonProperty diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/model/Model.java b/src/main/java/io/securecodebox/persistence/defectdojo/model/Model.java index 1b5af800..d41427c8 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/model/Model.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/model/Model.java @@ -6,5 +6,15 @@ * Interface for all models */ public interface Model { + /** + * Compares this model with the given query parameters + *

+ * The given query parameters are name value pairs, e.g.: + *

+ * + * TODO: Should we annotate queryParams with {@code @NonNull}? + * @param queryParams may be {@code null} + * @return {@code true} if equal, elas {@code false} + */ boolean equalsQueryString(Map queryParams); } diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/model/QueryParamsComparator.java b/src/main/java/io/securecodebox/persistence/defectdojo/model/QueryParamsComparator.java index 9715aae5..bf99bb94 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/model/QueryParamsComparator.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/model/QueryParamsComparator.java @@ -14,18 +14,55 @@ *

*/ final class QueryParamsComparator { - + /** + * Query parameter name for id + */ static final String QUERY_PARAM_KEY_FOR_ID = "id"; + /** + * Query parameter name for name + */ static final String QUERY_PARAM_KEY_FOR_NAME = "name"; + /** + * Hidden for pure static helper class + */ private QueryParamsComparator() { super(); } + /** + * Determines whether the given object is {@code null} or not + * + * @param o maybe {@code null} + * @return {@code true} if {@code o} is {@code null}, else {@code false} + */ static boolean isNull(Object o) { return o == null; } + /** + * Determines whether the {@link HasId id} of the given model object is equal the given {@link #QUERY_PARAM_KEY_FOR_ID id} + *

+ * Example: + *

+ *
+   *   {@code
+   *    final var model = ...
+   *    final var queryParams = new HashMap();
+   *    queryParams.put(QueryParamsComparator.QUERY_PARAM_KEY_FOR_ID, 42);
+   *
+   *    if (QueryParamsComparator.isIdEqual(model, queryParams)) {
+   *      ...
+   *    }
+   *   }
+   * 
+ *

+ * TODO: What about type conversions? The id is a long in the models, but it may be a string in the map. Should it be treated as equal (42 == "42")? + * + * @param model may be {@code null} + * @param queryParams may be {@code null} + * @return {@code true} id id is equal, else {@code false} + */ static boolean isIdEqual(HasId model, Map queryParams) { if (isNull(model)) { return false; @@ -46,6 +83,28 @@ static boolean isIdEqual(HasId model, Map queryParams) { return queryParams.get(QUERY_PARAM_KEY_FOR_ID).equals(model.getId()); } + /** + * Determines whether the {@link HasName name} of the given model object is equal the given {@link #QUERY_PARAM_KEY_FOR_NAME name} + *

+ * Example: + *

+ *
+   *   {@code
+   *    final var model = ...
+   *    final var queryParams = new HashMap();
+   *    queryParams.put(QueryParamsComparator.QUERY_PARAM_KEY_FOR_NAME, "foo");
+   *
+   *    if (QueryParamsComparator.isNameEqual(model, queryParams)) {
+   *      ...
+   *    }
+   *   }
+   * 
+ *

+ * + * @param model may be {@code null} + * @param queryParams may be {@code null} + * @return {@code true} if name is equal, else {@code false} + */ static boolean isNameEqual(HasName model, Map queryParams) { if (isNull(model)) { return false; 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 00bb63bd..8d8b2d8e 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/service/DefaultImportScanService.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/service/DefaultImportScanService.java @@ -5,7 +5,7 @@ package io.securecodebox.persistence.defectdojo.service; import io.securecodebox.persistence.defectdojo.ScanType; -import io.securecodebox.persistence.defectdojo.config.Config; +import io.securecodebox.persistence.defectdojo.config.ClientConfig; import io.securecodebox.persistence.defectdojo.exception.PersistenceException; import io.securecodebox.persistence.defectdojo.http.ProxyConfig; import io.securecodebox.persistence.defectdojo.model.ScanFile; @@ -56,13 +56,13 @@ class DefaultImportScanService implements ImportScanService { /** * Dedicated constructor. * - * @param config not {@code null} + * @param clientConfig not {@code null} * @param proxyConfig not {@code null} */ - DefaultImportScanService(final @NonNull Config config, @NonNull ProxyConfig proxyConfig) { + DefaultImportScanService(final @NonNull ClientConfig clientConfig, @NonNull ProxyConfig proxyConfig) { super(); - this.defectDojoUrl = config.getUrl(); - this.defectDojoApiKey = config.getApiKey(); + this.defectDojoUrl = clientConfig.getUrl(); + this.defectDojoApiKey = clientConfig.getApiKey(); this.proxyConfig = proxyConfig; } diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/service/DefectDojoService.java b/src/main/java/io/securecodebox/persistence/defectdojo/service/DefectDojoService.java index ed28a8e1..b5524a4a 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/service/DefectDojoService.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/service/DefectDojoService.java @@ -3,11 +3,9 @@ // SPDX-License-Identifier: Apache-2.0 package io.securecodebox.persistence.defectdojo.service; -import com.fasterxml.jackson.core.JsonProcessingException; import io.securecodebox.persistence.defectdojo.model.Model; import lombok.NonNull; -import java.net.URISyntaxException; import java.util.List; import java.util.Map; import java.util.Optional; @@ -33,19 +31,15 @@ public interface DefectDojoService { * * @param queryParams not {@code null} * @return not {@code null}, maybe empty - * @throws URISyntaxException - * @throws JsonProcessingException */ - List search(@NonNull Map queryParams) throws URISyntaxException, JsonProcessingException; + List search(@NonNull Map queryParams); /** * Get list of all model objects in DefectDojo * * @return never {@code null}, maybe empty - * @throws URISyntaxException - * @throws JsonProcessingException */ - List search() throws URISyntaxException, JsonProcessingException; + List search(); /** * Search for a single model object in DefectDojo @@ -55,10 +49,8 @@ public interface DefectDojoService { * * @param searchObject not {@code null} * @return never {@code null} - * @throws URISyntaxException - * @throws JsonProcessingException */ - Optional searchUnique(@NonNull T searchObject) throws URISyntaxException, JsonProcessingException; + Optional searchUnique(@NonNull T searchObject); /** * Search for a single model object in DefectDojo @@ -68,10 +60,8 @@ public interface DefectDojoService { * * @param queryParams not {@code null} * @return never {@code null} - * @throws URISyntaxException - * @throws JsonProcessingException */ - Optional searchUnique(@NonNull Map queryParams) throws URISyntaxException, JsonProcessingException; + Optional searchUnique(@NonNull Map queryParams); /** * Create the given model object in DefectDojo diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/service/EndpointService.java b/src/main/java/io/securecodebox/persistence/defectdojo/service/EndpointService.java index 97fc20c3..ed29d5a7 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/service/EndpointService.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/service/EndpointService.java @@ -6,14 +6,15 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; -import io.securecodebox.persistence.defectdojo.config.Config; +import io.securecodebox.persistence.defectdojo.config.ClientConfig; +import io.securecodebox.persistence.defectdojo.exception.PersistenceException; import io.securecodebox.persistence.defectdojo.model.Endpoint; import io.securecodebox.persistence.defectdojo.model.PaginatedResult; import lombok.NonNull; public class EndpointService extends GenericDefectDojoService { - public EndpointService(Config config) { - super(config); + public EndpointService(ClientConfig clientConfig) { + super(clientConfig); } @Override @@ -27,8 +28,12 @@ protected Class getModelClass() { } @Override - protected PaginatedResult deserializeList(@NonNull String response) throws JsonProcessingException { - return this.objectMapper.readValue(response, new TypeReference<>() { - }); + protected PaginatedResult deserializeList(@NonNull String response) { + try { + return modelObjectMapper().readValue(response, new TypeReference<>() { + }); + } catch (JsonProcessingException e) { + throw new PersistenceException("Can't process JSON response!", e); + } } } diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/service/EngagementService.java b/src/main/java/io/securecodebox/persistence/defectdojo/service/EngagementService.java index 57877106..1f8521a7 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/service/EngagementService.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/service/EngagementService.java @@ -6,14 +6,15 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; -import io.securecodebox.persistence.defectdojo.config.Config; +import io.securecodebox.persistence.defectdojo.config.ClientConfig; +import io.securecodebox.persistence.defectdojo.exception.PersistenceException; import io.securecodebox.persistence.defectdojo.model.Engagement; import io.securecodebox.persistence.defectdojo.model.PaginatedResult; import lombok.NonNull; public class EngagementService extends GenericDefectDojoService { - public EngagementService(Config config) { - super(config); + public EngagementService(ClientConfig clientConfig) { + super(clientConfig); } @Override @@ -27,8 +28,12 @@ protected Class getModelClass() { } @Override - protected PaginatedResult deserializeList(@NonNull String response) throws JsonProcessingException { - return this.objectMapper.readValue(response, new TypeReference<>() { - }); + protected PaginatedResult deserializeList(@NonNull String response) { + try { + return modelObjectMapper().readValue(response, new TypeReference<>() { + }); + } catch (JsonProcessingException e) { + throw new PersistenceException("Can't process JSON response!", e); + } } } diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/service/FindingService.java b/src/main/java/io/securecodebox/persistence/defectdojo/service/FindingService.java index 032a09f6..8d9950ee 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/service/FindingService.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/service/FindingService.java @@ -6,18 +6,18 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; -import io.securecodebox.persistence.defectdojo.config.Config; +import io.securecodebox.persistence.defectdojo.config.ClientConfig; +import io.securecodebox.persistence.defectdojo.exception.PersistenceException; import io.securecodebox.persistence.defectdojo.model.Finding; import io.securecodebox.persistence.defectdojo.model.PaginatedResult; import lombok.NonNull; -import java.net.URISyntaxException; import java.util.List; import java.util.Map; public class FindingService extends GenericDefectDojoService { - public FindingService(Config config) { - super(config); + public FindingService(ClientConfig clientConfig) { + super(clientConfig); } @Override @@ -31,12 +31,16 @@ protected Class getModelClass() { } @Override - protected PaginatedResult deserializeList(@NonNull String response) throws JsonProcessingException { - return this.objectMapper.readValue(response, new TypeReference<>() { - }); + protected PaginatedResult deserializeList(@NonNull String response) { + try { + return modelObjectMapper().readValue(response, new TypeReference<>() { + }); + } catch (JsonProcessingException e) { + throw new PersistenceException("Can't process JSON response!", e); + } } - public List getUnhandledFindingsForProduct(long productId, Finding.Severity minimumSeverity) throws URISyntaxException, JsonProcessingException { + public List getUnhandledFindingsForProduct(long productId, Finding.Severity minimumSeverity) { final Map queryParams = Map.of( "test__engagement__product", Long.toString(productId), "active", Boolean.toString(true)); @@ -47,7 +51,7 @@ public List getUnhandledFindingsForProduct(long productId, Finding.Seve .toList(); } - public List getUnhandledFindingsForEngagement(long engagementId, Finding.Severity minimumSeverity) throws URISyntaxException, JsonProcessingException { + public List getUnhandledFindingsForEngagement(long engagementId, Finding.Severity minimumSeverity) { final Map queryParams = Map.of( "test__engagement", Long.toString(engagementId), "active", Boolean.toString(true)); 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 2119c618..00d3902c 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/service/GenericDefectDojoService.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/service/GenericDefectDojoService.java @@ -4,20 +4,15 @@ package io.securecodebox.persistence.defectdojo.service; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.cfg.CoercionAction; -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.config.ClientConfig; +import io.securecodebox.persistence.defectdojo.exception.TooManyResponsesException; +import io.securecodebox.persistence.defectdojo.http.AuthHeaderFactory; +import io.securecodebox.persistence.defectdojo.http.ProxyConfig; import io.securecodebox.persistence.defectdojo.http.ProxyConfigFactory; -import io.securecodebox.persistence.defectdojo.model.Engagement; +import io.securecodebox.persistence.defectdojo.http.RestTemplateFactory; import io.securecodebox.persistence.defectdojo.model.Model; import io.securecodebox.persistence.defectdojo.model.PaginatedResult; -import lombok.Getter; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpEntity; @@ -33,47 +28,50 @@ import org.springframework.web.util.UriComponentsBuilder; import java.net.URI; -import java.net.URISyntaxException; import java.util.*; -// TODO: Remove JsonProcessingException, URISyntaxException from public API and use a own runtime exception type bc these checked exceptions clutter the client coe. +/** + * Generic base implementation with common functionality shared by services + * + * @param type of model the service handles + */ @Slf4j abstract class GenericDefectDojoService implements DefectDojoService { - private static final String API_PREFIX = "/api/v2/"; private static final long DEFECT_DOJO_OBJET_LIMIT = 100L; - protected Config config; + private final ClientConfig clientConfig; + private final ProxyConfig proxyConfig; + private final RestTemplate restTemplate; + private final Mappers mapper = new Mappers(); - protected ObjectMapper objectMapper; - protected ObjectMapper searchStringMapper; - - @Getter - protected RestTemplate restTemplate; + /** + * Convenience constructor which initializes {@link #proxyConfig} + * + * @param clientConfig not {@code null} + */ + public GenericDefectDojoService(ClientConfig clientConfig) { + this(clientConfig, new ProxyConfigFactory().create()); + } - public GenericDefectDojoService(@NonNull Config config) { + /** + * Dedicated constructor + * + * @param clientConfig not {@code null} + * @param proxyConfig not {@code null} + */ + public GenericDefectDojoService(@NonNull ClientConfig clientConfig, @NonNull ProxyConfig proxyConfig) { super(); - this.config = config; - - this.objectMapper = new ObjectMapper(); - this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - this.objectMapper.coercionConfigFor(Engagement.Status.class).setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsNull); - this.objectMapper.findAndRegisterModules(); - - this.searchStringMapper = new ObjectMapper(); - this.searchStringMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - this.searchStringMapper.coercionConfigFor(Engagement.Status.class).setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsNull); - this.searchStringMapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT); - - this.restTemplate = this.setupRestTemplate(); + this.clientConfig = clientConfig; + this.proxyConfig = proxyConfig; + this.restTemplate = setupRestTemplate(); } @Override public final T get(long id) { - var restTemplate = this.getRestTemplate(); - HttpEntity payload = new HttpEntity<>(getDefectDojoAuthorizationHeaders()); + final HttpEntity payload = createRequestEntity(createAuthorizationHeaders()); - final var url = this.config.getUrl() + API_PREFIX + this.getUrlPath() + "/" + id; + final var url = createBaseUrl().resolve(String.valueOf(id)); log.debug("Requesting URL: {}", url); - ResponseEntity response = restTemplate.exchange( + final ResponseEntity response = restTemplate.exchange( url, HttpMethod.GET, payload, @@ -84,35 +82,40 @@ public final T get(long id) { } @Override - public final List search(@NonNull Map queryParams) throws URISyntaxException, JsonProcessingException { - List objects = new LinkedList<>(); + public final List search() { + return search(Collections.emptyMap()); + } + + @Override + public final List search(@NonNull Map queryParams) { + final List objects = new LinkedList<>(); boolean hasNext; long page = 0; do { - var response = internalSearch(queryParams, DEFECT_DOJO_OBJET_LIMIT, DEFECT_DOJO_OBJET_LIMIT * page++); + final var response = internalSearch(queryParams, DEFECT_DOJO_OBJET_LIMIT, DEFECT_DOJO_OBJET_LIMIT * page++); objects.addAll(response.getResults()); - hasNext = response.getNext() != null; - if (page > this.config.getMaxPageCountForGets()) { - throw new LoopException("Found too many response object. Quitting after " + (page - 1) + " paginated API pages of " + DEFECT_DOJO_OBJET_LIMIT + " each."); + + if (page > this.clientConfig.getMaxPageCountForGets()) { + final var msg = String.format( + "Found too many response object. Quitting after %d paginated API pages of %d each.", + page - 1, + DEFECT_DOJO_OBJET_LIMIT + ); + throw new TooManyResponsesException(msg); } } while (hasNext); return objects; } - @Override - public final List search() throws URISyntaxException, JsonProcessingException { - return search(new LinkedHashMap<>()); - } - @Override @SuppressWarnings("unchecked") - public final Optional searchUnique(T searchObject) throws URISyntaxException, JsonProcessingException { - Map queryParams = searchStringMapper.convertValue(searchObject, Map.class); - - var objects = search(queryParams); + public final Optional searchUnique(@NonNull T searchObject) { + final Map queryParams = mapper.searchStringMapper() + .convertValue(searchObject, Map.class); + final var objects = search(queryParams); return objects.stream() .filter(object -> object != null && object.equalsQueryString(queryParams)) @@ -120,42 +123,48 @@ public final Optional searchUnique(T searchObject) throws URISyntaxException, } @Override - public final Optional searchUnique(@NonNull Map queryParams) throws URISyntaxException, JsonProcessingException { - var objects = search(queryParams); + public final Optional searchUnique(@NonNull Map queryParams) { + final var found = search(queryParams); - return objects.stream() + return found.stream() .filter(object -> object.equalsQueryString(queryParams)) .findFirst(); } @Override public final T create(@NonNull T object) { - var restTemplate = this.getRestTemplate(); - HttpEntity payload = new HttpEntity<>(object, getDefectDojoAuthorizationHeaders()); + final HttpEntity payload = createRequestEntity(object, createAuthorizationHeaders()); + final ResponseEntity response = restTemplate.exchange(createBaseUrl(), HttpMethod.POST, payload, getModelClass()); - ResponseEntity response = restTemplate.exchange(this.config.getUrl() + API_PREFIX + getUrlPath() + "/", HttpMethod.POST, payload, getModelClass()); return response.getBody(); } @Override public final void delete(long id) { - var restTemplate = this.getRestTemplate(); - HttpEntity payload = new HttpEntity<>(getDefectDojoAuthorizationHeaders()); - - restTemplate.exchange(this.config.getUrl() + API_PREFIX + getUrlPath() + "/" + id + "/", HttpMethod.DELETE, payload, String.class); + final HttpEntity payload = createRequestEntity(createAuthorizationHeaders()); + final var url = createBaseUrl().resolve(id + "/"); + restTemplate.exchange(url, HttpMethod.DELETE, payload, String.class); } @Override public final T update(@NonNull T object, long id) { - var restTemplate = this.getRestTemplate(); - HttpEntity payload = new HttpEntity<>(object, getDefectDojoAuthorizationHeaders()); + final HttpEntity payload = createRequestEntity(object, createAuthorizationHeaders()); + final var url = createBaseUrl().resolve(id + "/"); + final ResponseEntity response = restTemplate.exchange(url, HttpMethod.PUT, payload, getModelClass()); - ResponseEntity response = restTemplate.exchange(this.config.getUrl() + API_PREFIX + getUrlPath() + "/" + id + "/", HttpMethod.PUT, payload, getModelClass()); return response.getBody(); } - + + private HttpEntity createRequestEntity(HttpHeaders headers) { + return createRequestEntity("", headers); + } + + private HttpEntity createRequestEntity(M body, HttpHeaders headers) { + return new HttpEntity<>(body, headers); + } + /** - * Get the URL path for the REST endpoint relative to {@link #API_PREFIX} + * Get the URL path for the REST endpoint relative to {@link ClientConfig#API_PREFIX} * * @return not {@code null}, not empty */ @@ -173,50 +182,63 @@ public final T update(@NonNull T object, long id) { * * @param response not {@code null}, maybe empty * @return not {@code null} - * @throws JsonProcessingException if string is not parsable as JSON */ - protected abstract PaginatedResult deserializeList(@NonNull String response) throws JsonProcessingException; + protected abstract PaginatedResult deserializeList(@NonNull String response); - /** - * @return The DefectDojo Authentication Header - */ - private HttpHeaders getDefectDojoAuthorizationHeaders() { - return new Foo(config, new ProxyConfigFactory().create()).generateAuthorizationHeaders(); + final URI createBaseUrl() { + final var buffer = clientConfig.getUrl() + + ClientConfig.API_PREFIX + + getUrlPath() + + '/'; + + return URI.create(buffer).normalize(); + } + + final ObjectMapper modelObjectMapper() { + // We only expose this mapper to subclasses. + return mapper.modelObjectMapper(); + } + + private HttpHeaders createAuthorizationHeaders() { + final var factory = new AuthHeaderFactory(clientConfig); + factory.setProxyConfig(proxyConfig); + return factory.generateAuthorizationHeaders(); } private RestTemplate setupRestTemplate() { - RestTemplate restTemplate = new Foo(config, new ProxyConfigFactory().create()).createRestTemplate(); - MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); - converter.setObjectMapper(this.objectMapper); - restTemplate.setMessageConverters(List.of( + final RestTemplate template = new RestTemplateFactory(proxyConfig).createRestTemplate(); + // TODO: Maybe all of this could be moved into the factory. + final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); + converter.setObjectMapper(mapper.modelObjectMapper()); + template.setMessageConverters(List.of( new FormHttpMessageConverter(), new ResourceHttpMessageConverter(), new StringHttpMessageConverter(), converter )); - return restTemplate; + return template; } - protected PaginatedResult internalSearch(Map queryParams, long limit, long offset) throws JsonProcessingException, URISyntaxException { - var restTemplate = this.getRestTemplate(); - HttpEntity payload = new HttpEntity<>(getDefectDojoAuthorizationHeaders()); - - var mutableQueryParams = new HashMap<>(queryParams); + protected PaginatedResult internalSearch(Map queryParams, long limit, long offset) { + final HttpEntity payload = createRequestEntity(createAuthorizationHeaders()); + final var mutableQueryParams = new HashMap<>(queryParams); mutableQueryParams.put("limit", String.valueOf(limit)); mutableQueryParams.put("offset", String.valueOf(offset)); - var multiValueMap = new LinkedMultiValueMap(); + final var multiValueMap = new LinkedMultiValueMap(); for (var entry : mutableQueryParams.entrySet()) { multiValueMap.set(entry.getKey(), String.valueOf(entry.getValue())); } - var url = new URI(this.config.getUrl() + API_PREFIX + this.getUrlPath() + "/"); - log.debug("Requesting URL: " + url); - var uriBuilder = UriComponentsBuilder.fromUri(url).queryParams(multiValueMap); + final var url = createBaseUrl(); + final UriComponentsBuilder builder; + builder = UriComponentsBuilder + .fromUri(url) + .queryParams(multiValueMap); - ResponseEntity responseString = restTemplate.exchange( - uriBuilder.build(mutableQueryParams), + final ResponseEntity responseString = restTemplate.exchange( + builder.build(mutableQueryParams), HttpMethod.GET, payload, String.class diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/service/GroupMemberService.java b/src/main/java/io/securecodebox/persistence/defectdojo/service/GroupMemberService.java index ecd2176b..2c1dfdf1 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/service/GroupMemberService.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/service/GroupMemberService.java @@ -6,14 +6,15 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; -import io.securecodebox.persistence.defectdojo.config.Config; +import io.securecodebox.persistence.defectdojo.config.ClientConfig; +import io.securecodebox.persistence.defectdojo.exception.PersistenceException; import io.securecodebox.persistence.defectdojo.model.GroupMember; import io.securecodebox.persistence.defectdojo.model.PaginatedResult; import lombok.NonNull; public class GroupMemberService extends GenericDefectDojoService { - public GroupMemberService(Config config) { - super(config); + public GroupMemberService(ClientConfig clientConfig) { + super(clientConfig); } @Override @@ -27,8 +28,12 @@ protected Class getModelClass() { } @Override - protected PaginatedResult deserializeList(@NonNull String response) throws JsonProcessingException { - return this.objectMapper.readValue(response, new TypeReference<>() { - }); + protected PaginatedResult deserializeList(@NonNull String response) { + try { + return modelObjectMapper().readValue(response, new TypeReference<>() { + }); + } catch (JsonProcessingException e) { + throw new PersistenceException("Can't process JSON response!", e); + } } } diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/service/GroupService.java b/src/main/java/io/securecodebox/persistence/defectdojo/service/GroupService.java index 0d5155b7..b2cdd0fd 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/service/GroupService.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/service/GroupService.java @@ -6,14 +6,15 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; -import io.securecodebox.persistence.defectdojo.config.Config; +import io.securecodebox.persistence.defectdojo.config.ClientConfig; +import io.securecodebox.persistence.defectdojo.exception.PersistenceException; import io.securecodebox.persistence.defectdojo.model.Group; import io.securecodebox.persistence.defectdojo.model.PaginatedResult; import lombok.NonNull; public class GroupService extends GenericDefectDojoService { - public GroupService(Config config) { - super(config); + public GroupService(ClientConfig clientConfig) { + super(clientConfig); } @Override @@ -27,8 +28,12 @@ protected Class getModelClass() { } @Override - protected PaginatedResult deserializeList(@NonNull String response) throws JsonProcessingException { - return this.objectMapper.readValue(response, new TypeReference<>() { - }); + protected PaginatedResult deserializeList(@NonNull String response) { + try { + return modelObjectMapper().readValue(response, new TypeReference<>() { + }); + } catch (JsonProcessingException e) { + throw new PersistenceException("Can't process JSON response!", e); + } } } 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 32ca3e5c..20150d93 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/service/ImportScanService.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/service/ImportScanService.java @@ -6,7 +6,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.securecodebox.persistence.defectdojo.ScanType; -import io.securecodebox.persistence.defectdojo.config.Config; +import io.securecodebox.persistence.defectdojo.config.ClientConfig; import io.securecodebox.persistence.defectdojo.http.ProxyConfig; import io.securecodebox.persistence.defectdojo.http.ProxyConfigFactory; import io.securecodebox.persistence.defectdojo.model.ScanFile; @@ -23,22 +23,22 @@ public interface ImportScanService { /** * Factory method to create new instance of service default implementation * - * @param config must not be {@code null} + * @param clientConfig must not be {@code null} * @return never {@code null} */ - static ImportScanService createDefault(final Config config) { - return createDefault(config, new ProxyConfigFactory().create()); + static ImportScanService createDefault(final ClientConfig clientConfig) { + return createDefault(clientConfig, new ProxyConfigFactory().create()); } /** * Factory method to create new instance of service default implementation * - * @param config must not be {@code null} + * @param clientConfig 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); + static ImportScanService createDefault(@NonNull final ClientConfig clientConfig, @NonNull final ProxyConfig proxyConfig) { + return new DefaultImportScanService(clientConfig, proxyConfig); } default ImportScanResponse importScan(ScanFile scanFile, long engagementId, long lead, String currentDate, ScanType scanType, long testType) { diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/service/ImportScanService2.java b/src/main/java/io/securecodebox/persistence/defectdojo/service/ImportScanService2.java index 28d809b3..b3081885 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/service/ImportScanService2.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/service/ImportScanService2.java @@ -6,9 +6,10 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.securecodebox.persistence.defectdojo.ScanType; -import io.securecodebox.persistence.defectdojo.config.Config; +import io.securecodebox.persistence.defectdojo.config.ClientConfig; import io.securecodebox.persistence.defectdojo.exception.PersistenceException; -import io.securecodebox.persistence.defectdojo.http.Foo; +import io.securecodebox.persistence.defectdojo.http.AuthHeaderFactory; +import io.securecodebox.persistence.defectdojo.http.RestTemplateFactory; import io.securecodebox.persistence.defectdojo.http.ProxyConfigFactory; import io.securecodebox.persistence.defectdojo.model.ScanFile; import lombok.Data; @@ -36,22 +37,24 @@ @Deprecated(forRemoval = true) public class ImportScanService2 { - private final Config config; + private final ClientConfig clientConfig; - public ImportScanService2(Config config) { + public ImportScanService2(ClientConfig clientConfig) { super(); - this.config = config; + this.clientConfig = clientConfig; } /** * @return The DefectDojo Authentication Header */ private HttpHeaders getDefectDojoAuthorizationHeaders() { - return new Foo(config, new ProxyConfigFactory().create()).generateAuthorizationHeaders(); + final var factory = new AuthHeaderFactory(clientConfig); + factory.setProxyConfig(new ProxyConfigFactory().create()); + return factory.generateAuthorizationHeaders(); } protected RestTemplate setupRestTemplate() { - return new Foo(config, new ProxyConfigFactory().create()).createRestTemplate(); + return new RestTemplateFactory(new ProxyConfigFactory().create()).createRestTemplate(); } /** @@ -93,7 +96,7 @@ public String getFilename() { var payload = new HttpEntity<>(mvn, headers); - return restTemplate.exchange(config.getUrl() + "/api/v2/" + endpoint + "/", HttpMethod.POST, payload, ImportScanResponse.class).getBody(); + return restTemplate.exchange(clientConfig.getUrl() + "/api/v2/" + endpoint + "/", HttpMethod.POST, payload, ImportScanResponse.class).getBody(); } catch (HttpClientErrorException e) { throw new PersistenceException("Failed to attach findings to engagement."); } diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/service/Mappers.java b/src/main/java/io/securecodebox/persistence/defectdojo/service/Mappers.java new file mode 100644 index 00000000..f5facea9 --- /dev/null +++ b/src/main/java/io/securecodebox/persistence/defectdojo/service/Mappers.java @@ -0,0 +1,40 @@ +package io.securecodebox.persistence.defectdojo.service; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.cfg.CoercionAction; +import com.fasterxml.jackson.databind.cfg.CoercionInputShape; +import io.securecodebox.persistence.defectdojo.model.Engagement; +import lombok.Getter; +import lombok.experimental.Accessors; + +/** + * Provides pre configured Jackson mappers + */ +@Getter +@Accessors(fluent = true) +final class Mappers { + private final ObjectMapper modelObjectMapper = new ObjectMapper(); + private final ObjectMapper searchStringMapper = new ObjectMapper(); + + Mappers() { + super(); + configureModelObjectMapper(); + configureSearchStringMapper(); + } + + private void configureModelObjectMapper() { + modelObjectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + modelObjectMapper.coercionConfigFor(Engagement.Status.class) + .setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsNull); + modelObjectMapper.findAndRegisterModules(); + } + + private void configureSearchStringMapper() { + searchStringMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + searchStringMapper.coercionConfigFor(Engagement.Status.class) + .setCoercion(CoercionInputShape.EmptyString, CoercionAction.AsNull); + searchStringMapper.setSerializationInclusion(JsonInclude.Include.NON_DEFAULT); + } +} diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/service/ProductGroupService.java b/src/main/java/io/securecodebox/persistence/defectdojo/service/ProductGroupService.java index 40c8faee..5bfe4898 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/service/ProductGroupService.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/service/ProductGroupService.java @@ -6,14 +6,15 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; -import io.securecodebox.persistence.defectdojo.config.Config; +import io.securecodebox.persistence.defectdojo.config.ClientConfig; +import io.securecodebox.persistence.defectdojo.exception.PersistenceException; import io.securecodebox.persistence.defectdojo.model.PaginatedResult; import io.securecodebox.persistence.defectdojo.model.ProductGroup; import lombok.NonNull; public class ProductGroupService extends GenericDefectDojoService { - public ProductGroupService(Config config) { - super(config); + public ProductGroupService(ClientConfig clientConfig) { + super(clientConfig); } @Override @@ -27,8 +28,12 @@ protected Class getModelClass() { } @Override - protected PaginatedResult deserializeList(@NonNull String response) throws JsonProcessingException { - return this.objectMapper.readValue(response, new TypeReference<>() { - }); + protected PaginatedResult deserializeList(@NonNull String response) { + try { + return modelObjectMapper().readValue(response, new TypeReference<>() { + }); + } catch (JsonProcessingException e) { + throw new PersistenceException("Can't process JSON response!", e); + } } } diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/service/ProductService.java b/src/main/java/io/securecodebox/persistence/defectdojo/service/ProductService.java index 77429c02..08c47ec1 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/service/ProductService.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/service/ProductService.java @@ -6,15 +6,16 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; -import io.securecodebox.persistence.defectdojo.config.Config; +import io.securecodebox.persistence.defectdojo.config.ClientConfig; +import io.securecodebox.persistence.defectdojo.exception.PersistenceException; import io.securecodebox.persistence.defectdojo.model.PaginatedResult; import io.securecodebox.persistence.defectdojo.model.Product; import lombok.NonNull; public class ProductService extends GenericDefectDojoService { - public ProductService(Config config) { - super(config); + public ProductService(ClientConfig clientConfig) { + super(clientConfig); } @Override @@ -28,8 +29,12 @@ protected Class getModelClass() { } @Override - protected PaginatedResult deserializeList(@NonNull String response) throws JsonProcessingException { - return this.objectMapper.readValue(response, new TypeReference<>() { - }); + protected PaginatedResult deserializeList(@NonNull String response) { + try { + return modelObjectMapper().readValue(response, new TypeReference<>() { + }); + } catch (JsonProcessingException e) { + throw new PersistenceException("Can't process JSON response!", e); + } } } diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/service/ProductTypeService.java b/src/main/java/io/securecodebox/persistence/defectdojo/service/ProductTypeService.java index fc8b5b0f..ae54229d 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/service/ProductTypeService.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/service/ProductTypeService.java @@ -6,15 +6,16 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; -import io.securecodebox.persistence.defectdojo.config.Config; +import io.securecodebox.persistence.defectdojo.config.ClientConfig; +import io.securecodebox.persistence.defectdojo.exception.PersistenceException; import io.securecodebox.persistence.defectdojo.model.PaginatedResult; import io.securecodebox.persistence.defectdojo.model.ProductType; import lombok.NonNull; public final class ProductTypeService extends GenericDefectDojoService { - public ProductTypeService(Config config) { - super(config); + public ProductTypeService(ClientConfig clientConfig) { + super(clientConfig); } @Override @@ -28,8 +29,12 @@ protected Class getModelClass() { } @Override - protected PaginatedResult deserializeList(@NonNull String response) throws JsonProcessingException { - return this.objectMapper.readValue(response, new TypeReference<>() { - }); + protected PaginatedResult deserializeList(@NonNull String response) { + try { + return modelObjectMapper().readValue(response, new TypeReference<>() { + }); + } catch (JsonProcessingException e) { + throw new PersistenceException("Can't process JSON response!", e); + } } } diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/service/TestService.java b/src/main/java/io/securecodebox/persistence/defectdojo/service/TestService.java index c715075c..34644d34 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/service/TestService.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/service/TestService.java @@ -6,14 +6,15 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; -import io.securecodebox.persistence.defectdojo.config.Config; +import io.securecodebox.persistence.defectdojo.config.ClientConfig; +import io.securecodebox.persistence.defectdojo.exception.PersistenceException; import io.securecodebox.persistence.defectdojo.model.PaginatedResult; import io.securecodebox.persistence.defectdojo.model.Test; import lombok.NonNull; public class TestService extends GenericDefectDojoService { - public TestService(Config config) { - super(config); + public TestService(ClientConfig clientConfig) { + super(clientConfig); } @Override @@ -27,8 +28,12 @@ protected Class getModelClass() { } @Override - protected PaginatedResult deserializeList(@NonNull String response) throws JsonProcessingException { - return this.objectMapper.readValue(response, new TypeReference<>() { - }); + protected PaginatedResult deserializeList(@NonNull String response) { + try { + return modelObjectMapper().readValue(response, new TypeReference<>() { + }); + } catch (JsonProcessingException e) { + throw new PersistenceException("Can't process JSON response!", e); + } } } diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/service/TestTypeService.java b/src/main/java/io/securecodebox/persistence/defectdojo/service/TestTypeService.java index fc463a7b..bd46becd 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/service/TestTypeService.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/service/TestTypeService.java @@ -6,14 +6,15 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; -import io.securecodebox.persistence.defectdojo.config.Config; +import io.securecodebox.persistence.defectdojo.config.ClientConfig; +import io.securecodebox.persistence.defectdojo.exception.PersistenceException; import io.securecodebox.persistence.defectdojo.model.PaginatedResult; import io.securecodebox.persistence.defectdojo.model.TestType; import lombok.NonNull; public class TestTypeService extends GenericDefectDojoService { - public TestTypeService(Config config) { - super(config); + public TestTypeService(ClientConfig clientConfig) { + super(clientConfig); } @Override @@ -27,8 +28,12 @@ protected Class getModelClass() { } @Override - protected PaginatedResult deserializeList(@NonNull String response) throws JsonProcessingException { - return this.objectMapper.readValue(response, new TypeReference<>() { - }); + protected PaginatedResult deserializeList(@NonNull String response) { + try { + return modelObjectMapper().readValue(response, new TypeReference<>() { + }); + } catch (JsonProcessingException e) { + throw new PersistenceException("Can't process JSON response!", e); + } } } diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/service/ToolConfigService.java b/src/main/java/io/securecodebox/persistence/defectdojo/service/ToolConfigService.java index 0212c45d..a016593f 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/service/ToolConfigService.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/service/ToolConfigService.java @@ -6,14 +6,15 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; -import io.securecodebox.persistence.defectdojo.config.Config; +import io.securecodebox.persistence.defectdojo.config.ClientConfig; +import io.securecodebox.persistence.defectdojo.exception.PersistenceException; import io.securecodebox.persistence.defectdojo.model.PaginatedResult; import io.securecodebox.persistence.defectdojo.model.ToolConfig; import lombok.NonNull; public class ToolConfigService extends GenericDefectDojoService { - public ToolConfigService(Config config) { - super(config); + public ToolConfigService(ClientConfig clientConfig) { + super(clientConfig); } @Override @@ -27,8 +28,12 @@ protected Class getModelClass() { } @Override - protected PaginatedResult deserializeList(@NonNull String response) throws JsonProcessingException { - return this.objectMapper.readValue(response, new TypeReference<>() { - }); + protected PaginatedResult deserializeList(@NonNull String response) { + try { + return modelObjectMapper().readValue(response, new TypeReference<>() { + }); + } catch (JsonProcessingException e) { + throw new PersistenceException("Can't process JSON response!", e); + } } } diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/service/ToolTypeService.java b/src/main/java/io/securecodebox/persistence/defectdojo/service/ToolTypeService.java index 1941fb4f..3c8d96d6 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/service/ToolTypeService.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/service/ToolTypeService.java @@ -6,7 +6,8 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; -import io.securecodebox.persistence.defectdojo.config.Config; +import io.securecodebox.persistence.defectdojo.config.ClientConfig; +import io.securecodebox.persistence.defectdojo.exception.PersistenceException; import io.securecodebox.persistence.defectdojo.model.PaginatedResult; import io.securecodebox.persistence.defectdojo.model.ToolType; import lombok.NonNull; @@ -20,8 +21,8 @@ public class ToolTypeService extends GenericDefectDojoService { @Deprecated(forRemoval = true) // Unused public static final String SECURITY_TEST_SERVER_NAME = "Security Test Orchestration Engine"; - public ToolTypeService(Config config) { - super(config); + public ToolTypeService(ClientConfig clientConfig) { + super(clientConfig); } @Override @@ -35,8 +36,12 @@ protected Class getModelClass() { } @Override - protected PaginatedResult deserializeList(@NonNull String response) throws JsonProcessingException { - return this.objectMapper.readValue(response, new TypeReference<>() { - }); + protected PaginatedResult deserializeList(@NonNull String response) { + try { + return modelObjectMapper().readValue(response, new TypeReference<>() { + }); + } catch (JsonProcessingException e) { + throw new PersistenceException("Can't process JSON response!", e); + } } } diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/service/UserProfileService.java b/src/main/java/io/securecodebox/persistence/defectdojo/service/UserProfileService.java index 2df7d424..20244bd1 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/service/UserProfileService.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/service/UserProfileService.java @@ -6,7 +6,8 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; -import io.securecodebox.persistence.defectdojo.config.Config; +import io.securecodebox.persistence.defectdojo.config.ClientConfig; +import io.securecodebox.persistence.defectdojo.exception.PersistenceException; import io.securecodebox.persistence.defectdojo.model.PaginatedResult; import io.securecodebox.persistence.defectdojo.model.UserProfile; import lombok.NonNull; @@ -15,8 +16,8 @@ public final class UserProfileService extends GenericDefectDojoService { - public UserProfileService(Config config) { - super(config); + public UserProfileService(ClientConfig clientConfig) { + super(clientConfig); } @Override @@ -30,13 +31,18 @@ protected Class getModelClass() { } @Override - protected PaginatedResult deserializeList(@NonNull String response) throws JsonProcessingException { + protected PaginatedResult deserializeList(@NonNull String response) { /* GenericDefectDojoService expects that the response from the defectdojo api is a list. * This endpoint returns a single object though, to not break the code this response * gets converted to a defectdojo response. */ - final var userProfile = this.objectMapper.readValue(response, new TypeReference() { - }); + final UserProfile userProfile; + try { + userProfile = modelObjectMapper().readValue(response, new TypeReference<>() { + }); + } catch (JsonProcessingException e) { + throw new PersistenceException("Can't process JSON response!", e); + } final var result = new PaginatedResult(); result.setResults(List.of(userProfile)); result.setCount(1); diff --git a/src/main/java/io/securecodebox/persistence/defectdojo/service/UserService.java b/src/main/java/io/securecodebox/persistence/defectdojo/service/UserService.java index 5e666f3d..83375e12 100644 --- a/src/main/java/io/securecodebox/persistence/defectdojo/service/UserService.java +++ b/src/main/java/io/securecodebox/persistence/defectdojo/service/UserService.java @@ -6,14 +6,15 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; -import io.securecodebox.persistence.defectdojo.config.Config; +import io.securecodebox.persistence.defectdojo.config.ClientConfig; +import io.securecodebox.persistence.defectdojo.exception.PersistenceException; import io.securecodebox.persistence.defectdojo.model.PaginatedResult; import io.securecodebox.persistence.defectdojo.model.User; import lombok.NonNull; public class UserService extends GenericDefectDojoService { - public UserService(Config config) { - super(config); + public UserService(ClientConfig clientConfig) { + super(clientConfig); } @Override @@ -27,8 +28,12 @@ protected Class getModelClass() { } @Override - protected PaginatedResult deserializeList(@NonNull String response) throws JsonProcessingException { - return this.objectMapper.readValue(response, new TypeReference<>() { - }); + protected PaginatedResult deserializeList(@NonNull String response) { + try { + return modelObjectMapper().readValue(response, new TypeReference<>() { + }); + } catch (JsonProcessingException e) { + throw new PersistenceException("Can't process JSON response!", e); + } } } diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/config/ConfigTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/config/ClientConfigTest.java similarity index 80% rename from src/test/java/io/securecodebox/persistence/defectdojo/config/ConfigTest.java rename to src/test/java/io/securecodebox/persistence/defectdojo/config/ClientConfigTest.java index 8ffa3de0..4052c871 100644 --- a/src/test/java/io/securecodebox/persistence/defectdojo/config/ConfigTest.java +++ b/src/test/java/io/securecodebox/persistence/defectdojo/config/ClientConfigTest.java @@ -4,7 +4,7 @@ package io.securecodebox.persistence.defectdojo.config; -import io.securecodebox.persistence.defectdojo.exception.ConfigException; +import io.securecodebox.persistence.defectdojo.exception.ClientConfigException; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; @@ -20,10 +20,10 @@ import static org.junit.jupiter.api.Assertions.assertThrows; /** - * Tests for {@link Config} + * Tests for {@link ClientConfig} */ @ExtendWith(SystemStubsExtension.class) -class ConfigTest { +class ClientConfigTest { @SystemStub private EnvironmentVariables environmentVariables; @@ -31,7 +31,7 @@ class ConfigTest { @Test void constructor_urlMustNotBeNull() { final var thrown = assertThrows(NullPointerException.class, () -> { - new Config(null, "apiKey", 1); + new ClientConfig(null, "apiKey", 1); }); assertThat(thrown.getMessage(), startsWith("url ")); @@ -40,7 +40,7 @@ void constructor_urlMustNotBeNull() { @Test void constructor_apiKeyMustNotBeNull() { final var thrown = assertThrows(NullPointerException.class, () -> { - new Config("url", null, 1); + new ClientConfig("url", null, 1); }); assertThat(thrown.getMessage(), startsWith("apiKey ")); @@ -50,7 +50,7 @@ void constructor_apiKeyMustNotBeNull() { @ValueSource(ints = {0, -1, -2, -23, -42, Integer.MIN_VALUE}) void constructor_maxPageCountForGetsMustNotBeLessThanOne(final int number) { final var thrown = assertThrows(IllegalArgumentException.class, () -> { - new Config("url", "apiKey", number); + new ClientConfig("url", "apiKey", number); }); assertThat(thrown.getMessage(), startsWith("maxPageCountForGets ")); @@ -63,7 +63,7 @@ void fromEnv() { .set("DEFECTDOJO_APIKEY", "apikey") .set("DEFECTDOJO_MAX_PAGE_COUNT_FOR_GETS", "23"); - final var sut = Config.fromEnv(); + final var sut = ClientConfig.fromEnv(); assertAll( () -> assertThat(sut.getUrl(), is("url")), @@ -77,7 +77,7 @@ void fromEnv_throwsExceptionIfNoUrlSet() { environmentVariables .set("DEFECTDOJO_APIKEY", "apikey"); - final var thrown = assertThrows(ConfigException.class, Config::fromEnv); + final var thrown = assertThrows(ClientConfigException.class, ClientConfig::fromEnv); assertThat(thrown.getMessage(), is("Missing environment variable 'DEFECTDOJO_URL'!")); } @@ -87,7 +87,7 @@ void fromEnv_throwsExceptionIfNoApiKeySet() { environmentVariables .set("DEFECTDOJO_URL", "url"); - final var thrown = assertThrows(ConfigException.class, Config::fromEnv); + final var thrown = assertThrows(ClientConfigException.class, ClientConfig::fromEnv); assertThat(thrown.getMessage(), is("Missing environment variable 'DEFECTDOJO_APIKEY'!")); } @@ -98,8 +98,8 @@ void fromEnv_usesDefaultIfNoMaxPageCountForGetSet() { .set("DEFECTDOJO_URL", "url") .set("DEFECTDOJO_APIKEY", "apikey"); - final var sut = Config.fromEnv(); - assertThat(sut.getMaxPageCountForGets(), is(Config.DEFAULT_MAX_PAGE_COUNT_FOR_GETS)); + final var sut = ClientConfig.fromEnv(); + assertThat(sut.getMaxPageCountForGets(), is(ClientConfig.DEFAULT_MAX_PAGE_COUNT_FOR_GETS)); } @Test @@ -109,7 +109,7 @@ void fromEnv_throwsExceptionIfMaxPageCountForGetIsNotParseableToInteger() { .set("DEFECTDOJO_APIKEY", "apikey") .set("DEFECTDOJO_MAX_PAGE_COUNT_FOR_GETS", "foo"); - final var thrown = assertThrows(ConfigException.class, Config::fromEnv); + final var thrown = assertThrows(ClientConfigException.class, ClientConfig::fromEnv); assertThat(thrown.getMessage(), is("Given value for environment variable 'DEFECTDOJO_MAX_PAGE_COUNT_FOR_GETS' is not a valid number! Given was 'foo'.")); } diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/http/AuthHeaderFactoryTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/http/AuthHeaderFactoryTest.java new file mode 100644 index 00000000..41cd7637 --- /dev/null +++ b/src/test/java/io/securecodebox/persistence/defectdojo/http/AuthHeaderFactoryTest.java @@ -0,0 +1,65 @@ +package io.securecodebox.persistence.defectdojo.http; + +import io.securecodebox.persistence.defectdojo.config.ClientConfig; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpHeaders; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Tests for {@link AuthHeaderFactory} + */ +class AuthHeaderFactoryTest { + private final ClientConfig clientConfig = new ClientConfig("url", "apikey"); + private final AuthHeaderFactory sut = new AuthHeaderFactory(clientConfig); + + @Test + void setProxyConfig_doesNotAllowNull() { + assertThrows(NullPointerException.class, () -> sut.setProxyConfig(null)); + } + + @Test + void generateAuthorizationHeaders_withoutProxyAuth() { + assertAll( + () -> assertThat( + sut.generateAuthorizationHeaders().get(HttpHeaders.AUTHORIZATION), + contains("Token apikey")), + () -> assertThat( + sut.generateAuthorizationHeaders().get(HttpHeaders.PROXY_AUTHORIZATION), + not(contains("Basic dXNlcjpwdw=="))) + ); + } + + @Test + void generateAuthorizationHeaders_withProxyAuth() { + final ProxyConfig proxyConfig = ProxyConfig.builder() + .user("user") + .password("pw") + .host("host") + .port(42) + .build(); + sut.setProxyConfig(proxyConfig); + + assertAll( + () -> assertThat( + sut.generateAuthorizationHeaders().get(HttpHeaders.AUTHORIZATION), + contains("Token apikey")), + () -> assertThat( + sut.generateAuthorizationHeaders().get(HttpHeaders.PROXY_AUTHORIZATION), + contains("Basic dXNlcjpwdw==")) + ); + } + + @Test + void encodeProxyCredentials() { + final var proxyConfig = ProxyConfig.builder() + .user("bärtram") + .password("gohze8Ae") + .build(); + + assertThat(sut.encodeProxyCredentials(proxyConfig), is("YsOkcnRyYW06Z29oemU4QWU=")); + } +} diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/http/FooTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/http/FooTest.java deleted file mode 100644 index 58f6e287..00000000 --- a/src/test/java/io/securecodebox/persistence/defectdojo/http/FooTest.java +++ /dev/null @@ -1,106 +0,0 @@ -// 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 org.junit.jupiter.api.Test; -import org.springframework.http.HttpHeaders; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; -import static org.junit.jupiter.api.Assertions.assertAll; - -/** - * Tests for {@link Foo} - */ -class FooTest { - private final Config config = new Config("url", "apikey"); - private final ProxyConfig proxyConfig = ProxyConfig.builder() - .user("user") - .password("pw") - .host("host") - .port(42) - .build(); - private final Foo sut = new Foo(config, proxyConfig); - - @Test - void generateAuthorizationHeaders_withoutProxyAuth() { - final var innerSut = new Foo(config, ProxyConfig.NULL); - - assertAll( - () -> assertThat( - innerSut.generateAuthorizationHeaders().get(HttpHeaders.AUTHORIZATION), - contains("Token apikey")), - () -> assertThat( - innerSut.generateAuthorizationHeaders().get(HttpHeaders.PROXY_AUTHORIZATION), - not(contains("Basic dXNlcjpwdw=="))) - ); - } - - @Test - void generateAuthorizationHeaders_withProxyAuth() { - final var innerSut = new Foo(config, proxyConfig); - - assertAll( - () -> assertThat( - innerSut.generateAuthorizationHeaders().get(HttpHeaders.AUTHORIZATION), - contains("Token apikey")), - () -> assertThat( - innerSut.generateAuthorizationHeaders().get(HttpHeaders.PROXY_AUTHORIZATION), - contains("Basic dXNlcjpwdw==")) - ); - } - - @Test - void encodeProxyCredentials() { - final var proxyConfig = ProxyConfig.builder() - .user("bärtram") - .password("gohze8Ae") - .build(); - - assertThat(Foo.encodeProxyCredentials(proxyConfig), is("YsOkcnRyYW06Z29oemU4QWU=")); - } - - @Test - void createCredentialsProvider() { - final var result = sut.createCredentialsProvider(); - final var credentials = result.getCredentials(sut.createAuthScope()); - - assertAll( - () -> assertThat(credentials.getUserPrincipal().getName(), is(proxyConfig.getUser())), - () -> assertThat(credentials.getPassword(), is(proxyConfig.getPassword())) - ); - } - - @Test - void createAuthScope() { - final var result = sut.createAuthScope(); - - assertAll( - () -> assertThat(result.getHost(), is(proxyConfig.getHost())), - () -> assertThat(result.getPort(), is(proxyConfig.getPort())) - ); - } - - @Test - void createCredentials() { - final var result = sut.createCredentials(); - - assertAll( - () -> assertThat(result.getUserPrincipal().getName(), is(proxyConfig.getUser())), - () -> assertThat(result.getPassword(), is(proxyConfig.getPassword())) - ); - } - - @Test - void createHttpHost() { - final var result = sut.createHttpHost(); - - assertAll( - () -> assertThat(result.getHostName(), is(proxyConfig.getHost())), - () -> assertThat(result.getPort(), is(proxyConfig.getPort())) - ); - } -} diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/http/ProxyCredentialFactoryTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/http/ProxyCredentialFactoryTest.java new file mode 100644 index 00000000..72ac3849 --- /dev/null +++ b/src/test/java/io/securecodebox/persistence/defectdojo/http/ProxyCredentialFactoryTest.java @@ -0,0 +1,51 @@ +package io.securecodebox.persistence.defectdojo.http; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertAll; + +/** + * Tests for {@link ProxyCredentialFactory} + */ +class ProxyCredentialFactoryTest { + private final ProxyConfig config = ProxyConfig.builder() + .user("user") + .password("pw") + .host("host") + .port(42) + .build(); + private final ProxyCredentialFactory sut = new ProxyCredentialFactory(config); + + @Test + void createCredentialsProvider() { + final var result = sut.createCredentialsProvider(); + final var credentials = result.getCredentials(sut.createAuthScope()); + + assertAll( + () -> assertThat(credentials.getUserPrincipal().getName(), is(config.getUser())), + () -> assertThat(credentials.getPassword(), is(config.getPassword())) + ); + } + + @Test + void createAuthScope() { + final var result = sut.createAuthScope(); + + assertAll( + () -> assertThat(result.getHost(), is(config.getHost())), + () -> assertThat(result.getPort(), is(config.getPort())) + ); + } + + @Test + void createCredentials() { + final var result = sut.createCredentials(); + + assertAll( + () -> assertThat(result.getUserPrincipal().getName(), is(config.getUser())), + () -> assertThat(result.getPassword(), is(config.getPassword())) + ); + } +} diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/http/RestTemplateFactoryTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/http/RestTemplateFactoryTest.java new file mode 100644 index 00000000..2cdb4d0a --- /dev/null +++ b/src/test/java/io/securecodebox/persistence/defectdojo/http/RestTemplateFactoryTest.java @@ -0,0 +1,36 @@ +// 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.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.assertAll; + +/** + * Tests for {@link RestTemplateFactory} + */ +class RestTemplateFactoryTest { + private final ProxyConfig proxyConfig = ProxyConfig.builder() + .user("user") + .password("pw") + .host("host") + .port(42) + .build(); + private final RestTemplateFactory sut = new RestTemplateFactory(proxyConfig); + + + + @Test + void createHttpHost() { + final var result = sut.createHttpHost(); + + assertAll( + () -> assertThat(result.getHostName(), is(proxyConfig.getHost())), + () -> assertThat(result.getPort(), is(proxyConfig.getPort())) + ); + } +} 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 3f78888a..40d7a37b 100644 --- a/src/test/java/io/securecodebox/persistence/defectdojo/service/DefaultImportScanServiceTest.java +++ b/src/test/java/io/securecodebox/persistence/defectdojo/service/DefaultImportScanServiceTest.java @@ -4,7 +4,7 @@ package io.securecodebox.persistence.defectdojo.service; -import io.securecodebox.persistence.defectdojo.config.Config; +import io.securecodebox.persistence.defectdojo.config.ClientConfig; import io.securecodebox.persistence.defectdojo.http.ProxyConfig; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -18,13 +18,13 @@ /** * Tests for {@link DefaultImportScanService} */ -final class DefaultImportScanServiceTest { - private final Config config = new Config( +class DefaultImportScanServiceTest { + private final ClientConfig clientConfig = new ClientConfig( "http://localhost", "apiKey", 23 ); - private final DefaultImportScanService sut = new DefaultImportScanService(config, ProxyConfig.NULL); + private final DefaultImportScanService sut = new DefaultImportScanService(clientConfig, ProxyConfig.NULL); @Test void constructorShouldThrowExceptionOnNullConfig() { @@ -36,7 +36,7 @@ void constructorShouldThrowExceptionOnNullConfig() { @Test void constructorShouldThrowExceptionOnNullProxyConfig() { assertThrows(NullPointerException.class, () -> { - new DefaultImportScanService(Config.NULL, null); + new DefaultImportScanService(ClientConfig.NULL, null); }); } @@ -57,7 +57,7 @@ void shouldConfigureProxySettings_trueIfProxyConfigIsComplete() { .host("host") .port(42) .build(); - final var innerSut = new DefaultImportScanService(config, proxyConfig); + final var innerSut = new DefaultImportScanService(clientConfig, proxyConfig); assertThat(innerSut.shouldConfigureProxySettings(), is(true)); } @@ -66,7 +66,7 @@ void shouldConfigureProxySettings_trueIfProxyConfigIsComplete() { void shouldConfigureProxySettings_falseIfProxyConfigIsIncomplete() { final var proxyConfig = ProxyConfig.builder() .build(); - final var innerSut = new DefaultImportScanService(config, proxyConfig); + final var innerSut = new DefaultImportScanService(clientConfig, proxyConfig); assertThat(innerSut.shouldConfigureProxySettings(), is(false)); } diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/service/EndpointServiceTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/service/EndpointServiceTest.java index 42f29516..28f97535 100644 --- a/src/test/java/io/securecodebox/persistence/defectdojo/service/EndpointServiceTest.java +++ b/src/test/java/io/securecodebox/persistence/defectdojo/service/EndpointServiceTest.java @@ -3,7 +3,6 @@ // SPDX-License-Identifier: Apache-2.0 package io.securecodebox.persistence.defectdojo.service; -import com.fasterxml.jackson.core.JsonProcessingException; import io.securecodebox.persistence.defectdojo.model.Endpoint; import org.junit.jupiter.api.Test; @@ -147,7 +146,7 @@ void get_byId() { @Test - void searchUnique_withSearchObjectWhichReturnsEmptyResult() throws URISyntaxException, JsonProcessingException { + void searchUnique_withSearchObjectWhichReturnsEmptyResult() { // Here we only test that the object properties are correctly mapped to get params, // since the response parsing and binding is covered by the other tests. stubFor(get(urlPathEqualTo("/api/v2/endpoints/")) @@ -173,7 +172,7 @@ void searchUnique_withSearchObjectWhichReturnsEmptyResult() throws URISyntaxExce } @Test - void searchUnique_withQueryParamsWhichReturnsEmptyResult() throws URISyntaxException, JsonProcessingException { + void searchUnique_withQueryParamsWhichReturnsEmptyResult() { // Here we only test that the object properties are correctly mapped to get params, // since the response parsing and binding is covered by the other tests. stubFor(get(urlPathEqualTo("/api/v2/endpoints/")) diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/service/EngagementServiceTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/service/EngagementServiceTest.java index 45f60c39..44904b11 100644 --- a/src/test/java/io/securecodebox/persistence/defectdojo/service/EngagementServiceTest.java +++ b/src/test/java/io/securecodebox/persistence/defectdojo/service/EngagementServiceTest.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import io.securecodebox.persistence.defectdojo.model.Engagement; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.io.IOException; diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/service/FindingServiceTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/service/FindingServiceTest.java index c323d07a..5db8aa7b 100644 --- a/src/test/java/io/securecodebox/persistence/defectdojo/service/FindingServiceTest.java +++ b/src/test/java/io/securecodebox/persistence/defectdojo/service/FindingServiceTest.java @@ -4,13 +4,8 @@ package io.securecodebox.persistence.defectdojo.service; -import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import io.securecodebox.persistence.defectdojo.model.Finding; -import io.securecodebox.persistence.defectdojo.model.RiskAcceptance; -import lombok.Builder; -import lombok.NonNull; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -18,7 +13,6 @@ import java.time.OffsetDateTime; import java.util.Collections; import java.util.HashMap; -import java.util.LinkedList; import java.util.List; import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/service/GenericDefectDojoServiceTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/service/GenericDefectDojoServiceTest.java new file mode 100644 index 00000000..4ea76646 --- /dev/null +++ b/src/test/java/io/securecodebox/persistence/defectdojo/service/GenericDefectDojoServiceTest.java @@ -0,0 +1,349 @@ +package io.securecodebox.persistence.defectdojo.service; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.type.TypeReference; +import io.securecodebox.persistence.defectdojo.config.ClientConfig; +import io.securecodebox.persistence.defectdojo.http.ProxyConfig; +import io.securecodebox.persistence.defectdojo.model.Model; +import io.securecodebox.persistence.defectdojo.model.PaginatedResult; +import lombok.NonNull; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpHeaders; + +import java.net.URI; +import java.util.Collections; +import java.util.Map; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +/** + * Tests for {@link GenericDefectDojoService} + */ +final class GenericDefectDojoServiceTest { + + private static final class TestModel implements Model { + @JsonProperty + private long id; + + @JsonProperty + private String name = ""; + + @Override + public boolean equalsQueryString(@NonNull Map queryParams) { + // Stub this to false because we do not test this method here. + return false; + } + } + + private static final class TestableGenericDefectDojoService extends GenericDefectDojoService { + private TestableGenericDefectDojoService(ClientConfig clientConfig) { + super(clientConfig); + } + + private TestableGenericDefectDojoService(@NonNull ClientConfig clientConfig, @NonNull ProxyConfig proxyConfig) { + super(clientConfig, proxyConfig); + } + + @Override + protected String getUrlPath() { + return "snafu"; + } + + @Override + protected Class getModelClass() { + return TestModel.class; + } + + @Override + @SneakyThrows + protected PaginatedResult deserializeList(@NonNull String response) { + return modelObjectMapper().readValue(response, new TypeReference<>() { + }); + } + } + + private static final String JSON_SINGLE_OBJECT = """ + {"id": 42, "name": "foo"} + """; + private static final String JSON_MULTIPLE_OBJECT = """ + { + "count": 1, + "next": null, + "previous": null, + "results": [ + {"id": 42, "name": "foo"} + ], + "prefetch": {} + } + """; + + private final ClientConfig clientConfig = new ClientConfig("https://defectdojo.example.com:8080","api-key"); + private final TestableGenericDefectDojoService sut = new TestableGenericDefectDojoService(clientConfig); + + @Test + void createBaseUrl() { + assertThat(sut.createBaseUrl(), is(URI.create("https://defectdojo.example.com:8080/api/v2/snafu/"))); + } + + @Nested + class AuthenticationHeaderWithoutProxyConfig extends WireMockBaseTestCase { + private final TestableGenericDefectDojoService sut = new TestableGenericDefectDojoService( + conf(), ProxyConfig.NULL + ); + + @Test + void get_containsAuthHeaderInRequest() { + stubFor(get(urlPathEqualTo("/api/v2/snafu/42")) + .willReturn(ok() + .withHeaders(responseHeaders(JSON_SINGLE_OBJECT.length())) + .withBody(JSON_SINGLE_OBJECT))); + + sut.get(42L); + + verify(getRequestedFor(urlPathEqualTo("/api/v2/snafu/42")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Token " + API_KEY)) + .withoutHeader(HttpHeaders.PROXY_AUTHORIZATION) + ); + } + + @Test + void search_containsAuthHeaderInRequest() { + stubFor(get(urlPathEqualTo("/api/v2/snafu/")) + .willReturn(ok() + .withHeaders(responseHeaders(JSON_MULTIPLE_OBJECT.length())) + .withBody(JSON_MULTIPLE_OBJECT))); + + sut.search(); + + verify(getRequestedFor(urlPathEqualTo("/api/v2/snafu/")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Token " + API_KEY)) + .withoutHeader(HttpHeaders.PROXY_AUTHORIZATION) + ); + } + + @Test + void searchWithQueryParams_containsAuthHeaderInRequest() { + stubFor(get(urlPathEqualTo("/api/v2/snafu/")) + .willReturn(ok() + .withHeaders(responseHeaders(JSON_MULTIPLE_OBJECT.length())) + .withBody(JSON_MULTIPLE_OBJECT))); + + sut.search(Collections.emptyMap()); + + verify(getRequestedFor(urlPathEqualTo("/api/v2/snafu/")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Token " + API_KEY)) + .withoutHeader(HttpHeaders.PROXY_AUTHORIZATION) + ); + } + + @Test + void searchUniqueWithObject_containsAuthHeaderInRequest() { + stubFor(get(urlPathEqualTo("/api/v2/snafu/")) + .willReturn(ok() + .withHeaders(responseHeaders(JSON_MULTIPLE_OBJECT.length())) + .withBody(JSON_MULTIPLE_OBJECT))); + + sut.searchUnique(new TestModel()); + + verify(getRequestedFor(urlPathEqualTo("/api/v2/snafu/")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Token " + API_KEY)) + .withoutHeader(HttpHeaders.PROXY_AUTHORIZATION) + ); + } + + @Test + void searchUniqueWithQueryParams_containsAuthHeaderInRequest() { + stubFor(get(urlPathEqualTo("/api/v2/snafu/")) + .willReturn(ok() + .withHeaders(responseHeaders(JSON_MULTIPLE_OBJECT.length())) + .withBody(JSON_MULTIPLE_OBJECT))); + + sut.searchUnique(Collections.emptyMap()); + + verify(getRequestedFor(urlPathEqualTo("/api/v2/snafu/")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Token " + API_KEY)) + .withoutHeader(HttpHeaders.PROXY_AUTHORIZATION) + ); + } + + @Test + void create_containsAuthHeaderInRequest() { + stubFor(post(urlPathEqualTo("/api/v2/snafu/")) + .willReturn(created() + .withHeaders(responseHeaders(JSON_SINGLE_OBJECT.length())) + .withBody(JSON_SINGLE_OBJECT))); + + sut.create(new TestModel()); + + verify(postRequestedFor(urlPathEqualTo("/api/v2/snafu/")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Token " + API_KEY)) + .withoutHeader(HttpHeaders.PROXY_AUTHORIZATION) + ); + } + + @Test + void delete_containsAuthHeaderInRequest() { + stubFor(delete(urlPathEqualTo("/api/v2/snafu/42/")) + .willReturn(ok())); + + sut.delete(42); + + verify(deleteRequestedFor(urlPathEqualTo("/api/v2/snafu/42/")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Token " + API_KEY)) + .withoutHeader(HttpHeaders.PROXY_AUTHORIZATION) + ); + } + + @Test + void update_containsAuthHeaderInRequest() { + stubFor(put(urlPathEqualTo("/api/v2/snafu/42/")) + .willReturn(ok() + .withHeaders(responseHeaders(JSON_SINGLE_OBJECT.length())) + .withBody(JSON_SINGLE_OBJECT))); + + sut.update(new TestModel(), 42); + + verify(putRequestedFor(urlPathEqualTo("/api/v2/snafu/42/")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Token " + API_KEY)) + .withoutHeader(HttpHeaders.PROXY_AUTHORIZATION) + ); + } + } + + @Nested + @Disabled("FIXME: All tests fail with this commit because Spring tries to connect via the proxy now 😬") + class AuthenticationHeaderWithProxyConfig extends WireMockBaseTestCase { + private final ProxyConfig proxyConfig = ProxyConfig.builder() + .user("alf") + .password("test1234") + .host("localhost") + .port(4444) + .build(); + private final TestableGenericDefectDojoService sut = new TestableGenericDefectDojoService( + conf(), proxyConfig + ); + + @Test + void get_containsAuthHeaderInRequest() { + stubFor(get(urlPathEqualTo("/api/v2/snafu/42")) + .willReturn(ok() + .withHeaders(responseHeaders(JSON_SINGLE_OBJECT.length())) + .withBody(JSON_SINGLE_OBJECT))); + + sut.get(42L); + + verify(getRequestedFor(urlPathEqualTo("/api/v2/snafu/42")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Token " + API_KEY)) + .withHeader(HttpHeaders.PROXY_AUTHORIZATION, equalTo("Basic YWxmOnRlc3QxMjM0")) + ); + } + + @Test + void search_containsAuthHeaderInRequest() { + stubFor(get(urlPathEqualTo("/api/v2/snafu/")) + .willReturn(ok() + .withHeaders(responseHeaders(JSON_MULTIPLE_OBJECT.length())) + .withBody(JSON_MULTIPLE_OBJECT))); + + sut.search(); + + verify(getRequestedFor(urlPathEqualTo("/api/v2/snafu/")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Token " + API_KEY)) + .withHeader(HttpHeaders.PROXY_AUTHORIZATION, equalTo("Basic YWxmOnRlc3QxMjM0")) + ); + } + + @Test + void searchWithQueryParams_containsAuthHeaderInRequest() { + stubFor(get(urlPathEqualTo("/api/v2/snafu/")) + .willReturn(ok() + .withHeaders(responseHeaders(JSON_MULTIPLE_OBJECT.length())) + .withBody(JSON_MULTIPLE_OBJECT))); + + sut.search(Collections.emptyMap()); + + verify(getRequestedFor(urlPathEqualTo("/api/v2/snafu/")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Token " + API_KEY)) + .withHeader(HttpHeaders.PROXY_AUTHORIZATION, equalTo("Basic YWxmOnRlc3QxMjM0")) + ); + } + + @Test + void searchUniqueWithObject_containsAuthHeaderInRequest() { + stubFor(get(urlPathEqualTo("/api/v2/snafu/")) + .willReturn(ok() + .withHeaders(responseHeaders(JSON_MULTIPLE_OBJECT.length())) + .withBody(JSON_MULTIPLE_OBJECT))); + + sut.searchUnique(new TestModel()); + + verify(getRequestedFor(urlPathEqualTo("/api/v2/snafu/")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Token " + API_KEY)) + .withHeader(HttpHeaders.PROXY_AUTHORIZATION, equalTo("Basic YWxmOnRlc3QxMjM0")) + ); + } + + @Test + void searchUniqueWithQueryParams_containsAuthHeaderInRequest() { + stubFor(get(urlPathEqualTo("/api/v2/snafu/")) + .willReturn(ok() + .withHeaders(responseHeaders(JSON_MULTIPLE_OBJECT.length())) + .withBody(JSON_MULTIPLE_OBJECT))); + + sut.searchUnique(Collections.emptyMap()); + + verify(getRequestedFor(urlPathEqualTo("/api/v2/snafu/")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Token " + API_KEY)) + .withHeader(HttpHeaders.PROXY_AUTHORIZATION, equalTo("Basic YWxmOnRlc3QxMjM0")) + ); + } + + @Test + void create_containsAuthHeaderInRequest() { + stubFor(post(urlPathEqualTo("/api/v2/snafu/")) + .willReturn(created() + .withHeaders(responseHeaders(JSON_SINGLE_OBJECT.length())) + .withBody(JSON_SINGLE_OBJECT))); + + sut.create(new TestModel()); + + verify(postRequestedFor(urlPathEqualTo("/api/v2/snafu/")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Token " + API_KEY)) + .withHeader(HttpHeaders.PROXY_AUTHORIZATION, equalTo("Basic YWxmOnRlc3QxMjM0")) + ); + } + + @Test + void delete_containsAuthHeaderInRequest() { + stubFor(delete(urlPathEqualTo("/api/v2/snafu/42/")) + .willReturn(ok())); + + sut.delete(42); + + verify(deleteRequestedFor(urlPathEqualTo("/api/v2/snafu/42/")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Token " + API_KEY)) + .withHeader(HttpHeaders.PROXY_AUTHORIZATION, equalTo("Basic YWxmOnRlc3QxMjM0")) + ); + } + + @Test + void update_containsAuthHeaderInRequest() { + stubFor(put(urlPathEqualTo("/api/v2/snafu/42/")) + .willReturn(ok() + .withHeaders(responseHeaders(JSON_SINGLE_OBJECT.length())) + .withBody(JSON_SINGLE_OBJECT))); + + sut.update(new TestModel(), 42); + + verify(putRequestedFor(urlPathEqualTo("/api/v2/snafu/42/")) + .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Token " + API_KEY)) + .withHeader(HttpHeaders.PROXY_AUTHORIZATION, equalTo("Basic YWxmOnRlc3QxMjM0")) + ); + } + } +} diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/service/GroupMemberServiceTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/service/GroupMemberServiceTest.java index 293fbc47..ae9d39c4 100644 --- a/src/test/java/io/securecodebox/persistence/defectdojo/service/GroupMemberServiceTest.java +++ b/src/test/java/io/securecodebox/persistence/defectdojo/service/GroupMemberServiceTest.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import io.securecodebox.persistence.defectdojo.model.GroupMember; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.io.IOException; diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/service/GroupServiceTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/service/GroupServiceTest.java index d65c5e72..0a417d84 100644 --- a/src/test/java/io/securecodebox/persistence/defectdojo/service/GroupServiceTest.java +++ b/src/test/java/io/securecodebox/persistence/defectdojo/service/GroupServiceTest.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import io.securecodebox.persistence.defectdojo.model.Group; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.io.IOException; 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 9a5a8aae..1e2d6f23 100644 --- a/src/test/java/io/securecodebox/persistence/defectdojo/service/ImportScanServiceTest.java +++ b/src/test/java/io/securecodebox/persistence/defectdojo/service/ImportScanServiceTest.java @@ -5,7 +5,7 @@ package io.securecodebox.persistence.defectdojo.service; import io.securecodebox.persistence.defectdojo.ScanType; -import io.securecodebox.persistence.defectdojo.config.Config; +import io.securecodebox.persistence.defectdojo.config.ClientConfig; import io.securecodebox.persistence.defectdojo.http.ProxyConfig; import io.securecodebox.persistence.defectdojo.model.ScanFile; import lombok.Getter; @@ -28,21 +28,21 @@ class ImportScanServiceTest { @Test void createDefault_throwsExceptionIfNullPassedInAsConfig() { - assertThrows(NullPointerException.class, () -> { - ImportScanService.createDefault(null, ProxyConfig.NULL); - }); + assertThrows( + NullPointerException.class, + () -> ImportScanService.createDefault(null, ProxyConfig.NULL)); } @Test void createDefault_throwsExceptionIfNullPassedInAsProxyConfig() { - assertThrows(NullPointerException.class, () -> { - ImportScanService.createDefault(Config.NULL, null); - }); + assertThrows( + NullPointerException.class, + () -> ImportScanService.createDefault(ClientConfig.NULL, null)); } @Test void createDefault_passesConfig() { - final var config = new Config( + final var config = new ClientConfig( "url", "apiKey", 23 diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/service/ProductServiceTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/service/ProductServiceTest.java index 625072ed..747d9b2c 100644 --- a/src/test/java/io/securecodebox/persistence/defectdojo/service/ProductServiceTest.java +++ b/src/test/java/io/securecodebox/persistence/defectdojo/service/ProductServiceTest.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import io.securecodebox.persistence.defectdojo.model.Product; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.io.IOException; diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/service/ProductTypeServiceTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/service/ProductTypeServiceTest.java index 297c7bc4..8698b1c8 100644 --- a/src/test/java/io/securecodebox/persistence/defectdojo/service/ProductTypeServiceTest.java +++ b/src/test/java/io/securecodebox/persistence/defectdojo/service/ProductTypeServiceTest.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import io.securecodebox.persistence.defectdojo.model.ProductType; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.io.IOException; diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/service/TestServiceTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/service/TestServiceTest.java index 3976ede5..0fd27937 100644 --- a/src/test/java/io/securecodebox/persistence/defectdojo/service/TestServiceTest.java +++ b/src/test/java/io/securecodebox/persistence/defectdojo/service/TestServiceTest.java @@ -7,7 +7,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import io.securecodebox.persistence.defectdojo.model.Test; -import org.junit.jupiter.api.Disabled; import java.io.IOException; import java.net.URISyntaxException; diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/service/TestTypeServiceTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/service/TestTypeServiceTest.java index ebbe4ada..47a3e299 100644 --- a/src/test/java/io/securecodebox/persistence/defectdojo/service/TestTypeServiceTest.java +++ b/src/test/java/io/securecodebox/persistence/defectdojo/service/TestTypeServiceTest.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import io.securecodebox.persistence.defectdojo.model.TestType; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.io.IOException; diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/service/ToolConfigServiceTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/service/ToolConfigServiceTest.java index fd7dce2f..ff14a5f4 100644 --- a/src/test/java/io/securecodebox/persistence/defectdojo/service/ToolConfigServiceTest.java +++ b/src/test/java/io/securecodebox/persistence/defectdojo/service/ToolConfigServiceTest.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import io.securecodebox.persistence.defectdojo.model.ToolConfig; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.io.IOException; diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/service/ToolTypeServiceTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/service/ToolTypeServiceTest.java index 9ba5cdcb..bd526e66 100644 --- a/src/test/java/io/securecodebox/persistence/defectdojo/service/ToolTypeServiceTest.java +++ b/src/test/java/io/securecodebox/persistence/defectdojo/service/ToolTypeServiceTest.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import io.securecodebox.persistence.defectdojo.model.ToolType; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.io.IOException; diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/service/UserServiceTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/service/UserServiceTest.java index 9a894779..a73992c5 100644 --- a/src/test/java/io/securecodebox/persistence/defectdojo/service/UserServiceTest.java +++ b/src/test/java/io/securecodebox/persistence/defectdojo/service/UserServiceTest.java @@ -5,7 +5,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import io.securecodebox.persistence.defectdojo.model.User; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.io.IOException; diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/service/WireMockBaseTestCase.java b/src/test/java/io/securecodebox/persistence/defectdojo/service/WireMockBaseTestCase.java index 7c397426..af999e04 100644 --- a/src/test/java/io/securecodebox/persistence/defectdojo/service/WireMockBaseTestCase.java +++ b/src/test/java/io/securecodebox/persistence/defectdojo/service/WireMockBaseTestCase.java @@ -6,7 +6,7 @@ import com.github.tomakehurst.wiremock.http.HttpHeader; import com.github.tomakehurst.wiremock.http.HttpHeaders; import com.github.tomakehurst.wiremock.junit5.WireMockTest; -import io.securecodebox.persistence.defectdojo.config.Config; +import io.securecodebox.persistence.defectdojo.config.ClientConfig; import lombok.Getter; import lombok.experimental.Accessors; @@ -25,6 +25,7 @@ @WireMockTest(httpPort = WireMockBaseTestCase.PORT) abstract class WireMockBaseTestCase { static final int PORT = 8888; + static final String API_KEY = "not-required-for-tests"; static final String EMPTY_SEARCH_RESULT_RESPONSE_FIXTURE = """ { "count": 0, @@ -36,9 +37,7 @@ abstract class WireMockBaseTestCase { """; private static final String FIXTURE_BASE_PACKAGE = "io/securecodebox/persistence/defectdojo/service"; - private final Config conf = new Config( - String.format("http://localhost:%d/", PORT), - "not-required-for-tests"); + private final ClientConfig conf = new ClientConfig(String.format("http://localhost:%d/", PORT), API_KEY); String readFixtureFile(String fixtureFile) throws IOException { final var fixtureFilePath = FIXTURE_BASE_PACKAGE + "/" + fixtureFile;