@@ -29,24 +30,12 @@ public final class Config { */ @NonNull private final String url; + /** * API key to authorize against the DefectDojo API. */ @NonNull private final String apiKey; - /** - * This name is used to set the creator of entities created in DefectDojo (findings etc.). - *
- * Since DefectDojo requires the id of the user this client lib must do a lookup to determine the according id. - * This does not work, if the user does nit have appropriate privileges. In this case you can set the {@link #userId} - * directly with the appropriate id of the user you want as creator. - *
- * - * @deprecated Must not be used anymore because we determine the userid via user_profile API endpoint. - */ - @NonNull - @Deprecated - private final String username; /** * How many pages of objects are fetched from the DefectDojo API @@ -59,36 +48,18 @@ public final class Config { */ private final int maxPageCountForGets; - /** - * Overwrite the creator by userid - *- * IMPORTANT: If this is set (not {@code null}) the {@link #username} is ignored! - *
- *- * This option is necessary, if the user belonging to the {@link #apiKey} has no privilege to determine it's userid. - *
- * - * @deprecated Must not be used anymore because we determine the userid via user_profile API endpoint. - */ - @Deprecated - private final Long userId; - /** * Dedicated constructor * * @param url not {@code null} * @param apiKey not {@code null} - * @param username not {@code null} * @param maxPageCountForGets not less than 1 - * @param userId may be {@code null} (see {@link #userId}) */ - public Config(final @NonNull String url, final @NonNull String apiKey, final @NonNull String username, final int maxPageCountForGets, final Long userId) { + public Config(final @NonNull String url, final @NonNull String apiKey, final int maxPageCountForGets) { super(); this.url = url; this.apiKey = apiKey; - this.username = username; this.maxPageCountForGets = validateIsGreaterZero(maxPageCountForGets, "maxPageCountForGets"); - this.userId = userId; } private static int validateIsGreaterZero(final int number, final String name) { @@ -99,18 +70,6 @@ private static int validateIsGreaterZero(final int number, final String name) { return number; } - /** - * Default constructor which sets {@link #userId} to {@code null} - * - * @param url not {@code null} - * @param apiKey not {@code null} - * @param username not {@code null} - * @param maxPageCountForGets not less than 1 - */ - public Config(final String url, final String apiKey, final String username, final int maxPageCountForGets) { - this(url, apiKey, username, maxPageCountForGets, null); - } - /** * Creates config from environment variables * @@ -118,19 +77,7 @@ public Config(final String url, final String apiKey, final String username, fina */ public static Config fromEnv() { final var url = findRequiredEnvVar(EnvVars.DEFECTDOJO_URL); - final var username = findRequiredEnvVar(EnvVars.DEFECTDOJO_USERNAME); final var apiKey = findRequiredEnvVar(EnvVars.DEFECTDOJO_APIKEY); - final Long userId; - - try { - userId = Optional.ofNullable(findEnvVar(EnvVars.DEFECTDOJO_USER_ID)) - .map(Long::parseLong).orElse(null); - } catch (final NumberFormatException e) { - throw new ConfigException( - String.format("Given user id for environment variable '%s' is not a valid id! Given was '%s'.", EnvVars.DEFECTDOJO_USER_ID.literal, findEnvVar(EnvVars.DEFECTDOJO_USER_ID)), - e); - } - final int maxPageCountForGets; if (hasEnvVar(EnvVars.DEFECTDOJO_MAX_PAGE_COUNT_FOR_GETS)) { @@ -144,7 +91,7 @@ public static Config fromEnv() { maxPageCountForGets = DEFAULT_MAX_PAGE_COUNT_FOR_GETS; } - return new Config(url, apiKey, username, maxPageCountForGets, userId); + return new Config(url, apiKey, maxPageCountForGets); } private static boolean hasEnvVar(final @NonNull EnvVars name) { @@ -169,9 +116,7 @@ private static String findRequiredEnvVar(final @NonNull EnvVars name) { */ public enum EnvVars { DEFECTDOJO_URL("DEFECTDOJO_URL"), - DEFECTDOJO_USERNAME("DEFECTDOJO_USERNAME"), DEFECTDOJO_APIKEY("DEFECTDOJO_APIKEY"), - DEFECTDOJO_USER_ID("DEFECTDOJO_USER_ID"), DEFECTDOJO_MAX_PAGE_COUNT_FOR_GETS("DEFECTDOJO_MAX_PAGE_COUNT_FOR_GETS"); /** * Literal name of configuration environment name diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/config/ConfigTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/config/ConfigTest.java index 3fa278cf..81f0d6b9 100644 --- a/src/test/java/io/securecodebox/persistence/defectdojo/config/ConfigTest.java +++ b/src/test/java/io/securecodebox/persistence/defectdojo/config/ConfigTest.java @@ -30,7 +30,7 @@ class ConfigTest { @Test void constructor_urlMustNotBeNull() { final var thrown = assertThrows(NullPointerException.class, () -> { - new Config(null, "apiKey", "username", 1, null); + new Config(null, "apiKey", 1); }); assertThat(thrown.getMessage(), startsWith("url ")); @@ -39,26 +39,17 @@ void constructor_urlMustNotBeNull() { @Test void constructor_apiKeyMustNotBeNull() { final var thrown = assertThrows(NullPointerException.class, () -> { - new Config("url", null, "username", 1, null); + new Config("url", null, 1); }); assertThat(thrown.getMessage(), startsWith("apiKey ")); } - @Test - void constructor_usernameMustNotBeNull() { - final var thrown = assertThrows(NullPointerException.class, () -> { - new Config("url", "apiKey", null, 1, null); - }); - - assertThat(thrown.getMessage(), startsWith("username ")); - } - @ParameterizedTest @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", "username", number, null); + new Config("url", "apiKey", number); }); assertThat(thrown.getMessage(), startsWith("maxPageCountForGets ")); @@ -66,74 +57,45 @@ void constructor_maxPageCountForGetsMustNotBeLessThanOne(final int number) { @Test void fromEnv() { - environmentVariables.set("DEFECTDOJO_URL", "url") - .set("DEFECTDOJO_USERNAME", "username") + environmentVariables + .set("DEFECTDOJO_URL", "url") .set("DEFECTDOJO_APIKEY", "apikey") - .set("DEFECTDOJO_USER_ID", "42") .set("DEFECTDOJO_MAX_PAGE_COUNT_FOR_GETS", "23"); final var sut = Config.fromEnv(); assertAll( () -> assertThat(sut.getUrl(), is("url")), - () -> assertThat(sut.getUsername(), is("username")), () -> assertThat(sut.getApiKey(), is("apikey")), - () -> assertThat(sut.getUserId(), is(42L)), () -> assertThat(sut.getMaxPageCountForGets(), is(23)) ); } @Test void fromEnv_throwsExceptionIfNoUrlSet() { - environmentVariables.set("DEFECTDOJO_USERNAME", "username") - .set("DEFECTDOJO_APIKEY", "apikey") - .set("DEFECTDOJO_USER_ID", "42"); + environmentVariables + .set("DEFECTDOJO_APIKEY", "apikey"); final var thrown = assertThrows(ConfigException.class, Config::fromEnv); assertThat(thrown.getMessage(), is("Missing environment variable 'DEFECTDOJO_URL'!")); } - @Test - void fromEnv_throwsExceptionIfNoUserNameSet() { - environmentVariables.set("DEFECTDOJO_URL", "url") - .set("DEFECTDOJO_APIKEY", "apikey") - .set("DEFECTDOJO_USER_ID", "42"); - - final var thrown = assertThrows(ConfigException.class, Config::fromEnv); - - assertThat(thrown.getMessage(), is("Missing environment variable 'DEFECTDOJO_USERNAME'!")); - } - @Test void fromEnv_throwsExceptionIfNoApiKeySet() { - environmentVariables.set("DEFECTDOJO_URL", "url") - .set("DEFECTDOJO_USERNAME", "username") - .set("DEFECTDOJO_USER_ID", "42"); + environmentVariables + .set("DEFECTDOJO_URL", "url"); final var thrown = assertThrows(ConfigException.class, Config::fromEnv); assertThat(thrown.getMessage(), is("Missing environment variable 'DEFECTDOJO_APIKEY'!")); } - @Test - void fromEnv_throwsExceptionIfUserIdIsNotParsableToLong() { - environmentVariables.set("DEFECTDOJO_URL", "url") - .set("DEFECTDOJO_USERNAME", "username") - .set("DEFECTDOJO_APIKEY", "apikey") - .set("DEFECTDOJO_USER_ID", "foo"); - - final var thrown = assertThrows(ConfigException.class, Config::fromEnv); - - assertThat(thrown.getMessage(), is("Given user id for environment variable 'DEFECTDOJO_USER_ID' is not a valid id! Given was 'foo'.")); - } - @Test void fromEnv_usesDefaultIfNoMaxPageCountForGetSet() { - environmentVariables.set("DEFECTDOJO_URL", "url") - .set("DEFECTDOJO_USERNAME", "username") - .set("DEFECTDOJO_APIKEY", "apikey") - .set("DEFECTDOJO_USER_ID", "42"); + environmentVariables + .set("DEFECTDOJO_URL", "url") + .set("DEFECTDOJO_APIKEY", "apikey"); final var sut = Config.fromEnv(); assertThat(sut.getMaxPageCountForGets(), is(Config.DEFAULT_MAX_PAGE_COUNT_FOR_GETS)); @@ -141,10 +103,9 @@ void fromEnv_usesDefaultIfNoMaxPageCountForGetSet() { @Test void fromEnv_throwsExceptionIfMaxPageCountForGetIsNotParseableToInteger() { - environmentVariables.set("DEFECTDOJO_URL", "url") - .set("DEFECTDOJO_USERNAME", "username") + environmentVariables + .set("DEFECTDOJO_URL", "url") .set("DEFECTDOJO_APIKEY", "apikey") - .set("DEFECTDOJO_USER_ID", "42") .set("DEFECTDOJO_MAX_PAGE_COUNT_FOR_GETS", "foo"); final var thrown = assertThrows(ConfigException.class, Config::fromEnv); 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 79387320..538eaff7 100644 --- a/src/test/java/io/securecodebox/persistence/defectdojo/service/DefaultImportScanServiceTest.java +++ b/src/test/java/io/securecodebox/persistence/defectdojo/service/DefaultImportScanServiceTest.java @@ -20,9 +20,7 @@ class DefaultImportScanServiceTest { private final Config config = new Config( "http://localhost", "apiKey", - "username", - 23, - 42L + 23 ); private final DefaultImportScanService sut = new DefaultImportScanService(config); 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 b0e425f9..db049097 100644 --- a/src/test/java/io/securecodebox/persistence/defectdojo/service/FindingServiceTest.java +++ b/src/test/java/io/securecodebox/persistence/defectdojo/service/FindingServiceTest.java @@ -8,130 +8,132 @@ import io.securecodebox.persistence.defectdojo.config.Config; import java.net.URISyntaxException; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.test.web.client.MockRestServiceServer; -import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; - +import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; import static org.junit.jupiter.api.Assertions.*; + import org.springframework.http.MediaType; + import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; // This test is sufficient for all services (except user profile) as all the code is generic -class FindingServiceTest{ +class FindingServiceTest { Config config; FindingService underTest; MockRestServiceServer mockServer; String findingResponse = """ - { - "count": 1, - "next": null, - "previous": null, - "results": - [ - { - "id": 42, - "tags": [], - "request_response": { - "req_resp": [] - }, - "accepted_risks": [], - "push_to_jira": false, - "age": 145, - "sla_days_remaining": null, - "finding_meta": [], - "related_fields": null, - "jira_creation": null, - "jira_change": null, - "display_status": "Active, Verified", - "finding_groups": [], - "title": "Open Port: 9929/TCP", - "date": "2021-03-18", - "sla_start_date": null, - "cwe": 0, - "cve": null, - "cvssv3": null, - "cvssv3_score": null, - "url": null, - "severity": "Info", - "description": "### Host\\n\\n**IP Address:** 198.51.100.0\\n**FQDN:** scanme.nmap.org\\n\\n\\n**Port/Protocol:** 9929/tcp\\n\\n\\n\\n\\n", - "mitigation": "N/A", - "impact": "No impact provided", - "steps_to_reproduce": null, - "severity_justification": null, - "references": null, - "is_template": false, - "active": true, - "verified": true, - "false_p": false, - "duplicate": false, - "out_of_scope": false, - "risk_accepted": false, - "under_review": false, - "last_status_update": "2021-07-21T12:43:36.628994Z", - "under_defect_review": false, - "is_mitigated": false, - "thread_id": 0, - "mitigated": null, - "numerical_severity": "S4", - "last_reviewed": "2021-07-21T12:43:36.545348Z", - "line_number": null, - "sourcefilepath": null, - "sourcefile": null, - "param": null, - "payload": null, - "hash_code": "8dbaad23d4056f0a97bb8f487795fe392b4124f28d4049d16430e43415f1c219", - "line": null, - "file_path": null, - "component_name": null, - "component_version": null, - "static_finding": false, - "dynamic_finding": true, - "created": "2021-07-21T12:43:36.549669Z", - "scanner_confidence": null, - "unique_id_from_tool": null, - "vuln_id_from_tool": null, - "sast_source_object": null, - "sast_sink_object": null, - "sast_source_line": null, - "sast_source_file_path": null, - "nb_occurences": null, - "publish_date": null, - "test": 222, - "duplicate_finding": null, - "review_requested_by": null, - "defect_review_requested_by": null, - "mitigated_by": null, - "reporter": 5, - "last_reviewed_by": 5, - "sonarqube_issue": null, - "endpoints": [ - 875 - ], - "endpoint_status": [ - 8640 - ], - "reviewers": [], - "notes": [], - "files": [], - "found_by": [ - 132 - ] - } - ], - "prefetch": {} - } - """; + { + "count": 1, + "next": null, + "previous": null, + "results": + [ + { + "id": 42, + "tags": [], + "request_response": { + "req_resp": [] + }, + "accepted_risks": [], + "push_to_jira": false, + "age": 145, + "sla_days_remaining": null, + "finding_meta": [], + "related_fields": null, + "jira_creation": null, + "jira_change": null, + "display_status": "Active, Verified", + "finding_groups": [], + "title": "Open Port: 9929/TCP", + "date": "2021-03-18", + "sla_start_date": null, + "cwe": 0, + "cve": null, + "cvssv3": null, + "cvssv3_score": null, + "url": null, + "severity": "Info", + "description": "### Host\\n\\n**IP Address:** 198.51.100.0\\n**FQDN:** scanme.nmap.org\\n\\n\\n**Port/Protocol:** 9929/tcp\\n\\n\\n\\n\\n", + "mitigation": "N/A", + "impact": "No impact provided", + "steps_to_reproduce": null, + "severity_justification": null, + "references": null, + "is_template": false, + "active": true, + "verified": true, + "false_p": false, + "duplicate": false, + "out_of_scope": false, + "risk_accepted": false, + "under_review": false, + "last_status_update": "2021-07-21T12:43:36.628994Z", + "under_defect_review": false, + "is_mitigated": false, + "thread_id": 0, + "mitigated": null, + "numerical_severity": "S4", + "last_reviewed": "2021-07-21T12:43:36.545348Z", + "line_number": null, + "sourcefilepath": null, + "sourcefile": null, + "param": null, + "payload": null, + "hash_code": "8dbaad23d4056f0a97bb8f487795fe392b4124f28d4049d16430e43415f1c219", + "line": null, + "file_path": null, + "component_name": null, + "component_version": null, + "static_finding": false, + "dynamic_finding": true, + "created": "2021-07-21T12:43:36.549669Z", + "scanner_confidence": null, + "unique_id_from_tool": null, + "vuln_id_from_tool": null, + "sast_source_object": null, + "sast_sink_object": null, + "sast_source_line": null, + "sast_source_file_path": null, + "nb_occurences": null, + "publish_date": null, + "test": 222, + "duplicate_finding": null, + "review_requested_by": null, + "defect_review_requested_by": null, + "mitigated_by": null, + "reporter": 5, + "last_reviewed_by": 5, + "sonarqube_issue": null, + "endpoints": [ + 875 + ], + "endpoint_status": [ + 8640 + ], + "reviewers": [], + "notes": [], + "files": [], + "found_by": [ + 132 + ] + } + ], + "prefetch": {} + } + """; @BeforeEach void setup() { - config = new Config("https://defectdojo.example.com", "abc", "test-user", 42); + config = new Config("https://defectdojo.example.com", "abc", 42); underTest = new FindingService(config); mockServer = MockRestServiceServer.createServer(underTest.getRestTemplate()); } @@ -145,12 +147,12 @@ void deserializeList() throws JsonProcessingException { @Test void testSearch() throws JsonProcessingException, URISyntaxException { - var url = config.getUrl() + "/api/v2/" + underTest.getUrlPath() + "/?offset=0&limit=100"; + var url = config.getUrl() + "/api/v2/" + underTest.getUrlPath() + "/?offset=0&limit=100"; mockServer.expect(requestTo(url)).andRespond(withSuccess(findingResponse, MediaType.APPLICATION_JSON)); - + var expected = underTest.deserializeList(findingResponse).getResults(); var actual = underTest.search(); - + mockServer.verify(); assertIterableEquals(expected, actual); } 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 4a54bd65..6263cf7c 100644 --- a/src/test/java/io/securecodebox/persistence/defectdojo/service/ImportScanServiceTest.java +++ b/src/test/java/io/securecodebox/persistence/defectdojo/service/ImportScanServiceTest.java @@ -36,9 +36,7 @@ void createDefault_passesConfig() { final var config = new Config( "url", "apiKey", - "username", - 23, - 42L + 23 ); final var sut = (DefaultImportScanService) ImportScanService.createDefault(config); @@ -52,7 +50,7 @@ void createDefault_passesConfig() { void importScan_withoutOptionsShouldPassEmptyMap() { assertThat(sut.getOptions(), is(nullValue())); - sut.importScan(new ScanFile("content"),42L, 43L, "1.1.2023", ScanType.AUDIT_JS_SCAN, 23L); + sut.importScan(new ScanFile("content"), 42L, 43L, "1.1.2023", ScanType.AUDIT_JS_SCAN, 23L); assertThat(sut.getOptions(), equalTo(Collections.EMPTY_MAP)); } @@ -61,7 +59,7 @@ void importScan_withoutOptionsShouldPassEmptyMap() { void importScan_withoutOptionsShouldPassModifiableMap() { assertThat(sut.getOptions(), is(nullValue())); - sut.importScan(new ScanFile("content"),42L, 43L, "1.1.2023", ScanType.AUDIT_JS_SCAN, 23L); + sut.importScan(new ScanFile("content"), 42L, 43L, "1.1.2023", ScanType.AUDIT_JS_SCAN, 23L); sut.getOptions().put("foo", "bar"); assertThat(sut.getOptions(), hasEntry("foo", "bar")); @@ -71,7 +69,7 @@ void importScan_withoutOptionsShouldPassModifiableMap() { void reimportScan_withoutOptionsShouldPassEmptyMap() { assertThat(sut.getOptions(), is(nullValue())); - sut.reimportScan(new ScanFile("content"),42L, 43L, "1.1.2023", ScanType.AUDIT_JS_SCAN, 23L); + sut.reimportScan(new ScanFile("content"), 42L, 43L, "1.1.2023", ScanType.AUDIT_JS_SCAN, 23L); assertThat(sut.getOptions(), equalTo(Collections.EMPTY_MAP)); } @@ -80,7 +78,7 @@ void reimportScan_withoutOptionsShouldPassEmptyMap() { void reimportScan_withoutOptionsShouldPassModifiableMap() { assertThat(sut.getOptions(), is(nullValue())); - sut.reimportScan(new ScanFile("content"),42L, 43L, "1.1.2023", ScanType.AUDIT_JS_SCAN, 23L); + sut.reimportScan(new ScanFile("content"), 42L, 43L, "1.1.2023", ScanType.AUDIT_JS_SCAN, 23L); sut.getOptions().put("foo", "bar"); assertThat(sut.getOptions(), hasEntry("foo", "bar")); diff --git a/src/test/java/io/securecodebox/persistence/defectdojo/service/UserProfileServiceTest.java b/src/test/java/io/securecodebox/persistence/defectdojo/service/UserProfileServiceTest.java index 7eb5bfc9..4fc42699 100644 --- a/src/test/java/io/securecodebox/persistence/defectdojo/service/UserProfileServiceTest.java +++ b/src/test/java/io/securecodebox/persistence/defectdojo/service/UserProfileServiceTest.java @@ -13,10 +13,12 @@ import java.util.Arrays; import static org.junit.jupiter.api.Assertions.assertIterableEquals; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; import org.springframework.test.web.client.MockRestServiceServer; + import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; @@ -24,45 +26,45 @@ // This test is special because the defectdojo api does not return a list, // but the generic code assumes every endpoint returns a list public class UserProfileServiceTest { - + Config config; UserProfileService underTest; MockRestServiceServer mockServer; - + // This string does not contain every field of the api response as those are not implemented String apiResponse = """ - { - "user": { - "id": 0, - "username": "GdqmXprK.j7R+OYE49SzL3mM2U6I0DyLRHnDg87i9It0AfP-kxvswW3qOI2i+31-@0", - "first_name": "string", - "last_name": "string", - "email": "user@example.com", - "last_login": "2022-11-01T16:20:19.373Z", - "is_active": true, - "is_superuser": true, - "configuration_permissions": [0] - } - } - """; - + { + "user": { + "id": 0, + "username": "GdqmXprK.j7R+OYE49SzL3mM2U6I0DyLRHnDg87i9It0AfP-kxvswW3qOI2i+31-@0", + "first_name": "string", + "last_name": "string", + "email": "user@example.com", + "last_login": "2022-11-01T16:20:19.373Z", + "is_active": true, + "is_superuser": true, + "configuration_permissions": [0] + } + } + """; + @BeforeEach void setup() { - config = new Config("https://defectdojo.example.com", "abc", "test-user", 42); + config = new Config("https://defectdojo.example.com", "abc", 42); underTest = new UserProfileService(config); mockServer = MockRestServiceServer.createServer(underTest.getRestTemplate()); } - + @Test void testSearch() throws JsonProcessingException, URISyntaxException { - var url = config.getUrl() + "/api/v2/" + underTest.getUrlPath() + "/?offset=0&limit=100"; + var url = config.getUrl() + "/api/v2/" + underTest.getUrlPath() + "/?offset=0&limit=100"; mockServer.expect(requestTo(url)).andRespond(withSuccess(apiResponse, MediaType.APPLICATION_JSON)); - + var user = new User(0L, "GdqmXprK.j7R+OYE49SzL3mM2U6I0DyLRHnDg87i9It0AfP-kxvswW3qOI2i+31-@0", "string", "string"); var userProfile = new UserProfile(user); var expected = Arrays.asList(userProfile); var actual = underTest.search(); - + mockServer.verify(); assertIterableEquals(expected, actual); }