diff --git a/sechub-adapter-pds/src/main/java/com/mercedesbenz/sechub/adapter/pds/PDSAdapterV1.java b/sechub-adapter-pds/src/main/java/com/mercedesbenz/sechub/adapter/pds/PDSAdapterV1.java index 1661e211ac..97d51ba16b 100644 --- a/sechub-adapter-pds/src/main/java/com/mercedesbenz/sechub/adapter/pds/PDSAdapterV1.java +++ b/sechub-adapter-pds/src/main/java/com/mercedesbenz/sechub/adapter/pds/PDSAdapterV1.java @@ -565,23 +565,28 @@ private String createJobDataJSON(PDSContext context) throws AdapterException { } private PDSJobData createJobData(PDSContext context) { - PDSAdapterConfig config = context.getConfig(); - PDSAdapterConfigData data = config.getPDSAdapterConfigData(); - assertConfigDataNotNull(data); - Map parameters = data.getJobParameters(); - + PDSAdapterConfig adapterConfig = context.getConfig(); + PDSAdapterConfigData adapterConfigData = adapterConfig.getPDSAdapterConfigData(); + assertConfigDataNotNull(adapterConfigData); + Map adapterConfigDataJobParameters = adapterConfigData.getJobParameters(); + + /* + * convert adapter configuration to PDS job data that shall be sent to PDS as + * key value parameters: + */ PDSJobData jobData = new PDSJobData(); - for (String key : parameters.keySet()) { + + for (String key : adapterConfigDataJobParameters.keySet()) { PDSJobParameterEntry parameter = new PDSJobParameterEntry(); parameter.key = key; - parameter.value = parameters.get(key); + parameter.value = adapterConfigDataJobParameters.get(key); jobData.parameters.add(parameter); } - UUID secHubJobUUID = data.getSecHubJobUUID(); + UUID secHubJobUUID = adapterConfigData.getSecHubJobUUID(); jobData.sechubJobUUID = secHubJobUUID.toString(); - jobData.productId = data.getPdsProductIdentifier(); + jobData.productId = adapterConfigData.getPdsProductIdentifier(); return jobData; } diff --git a/sechub-adapter/src/main/java/com/mercedesbenz/sechub/adapter/AbstractAdapterConfigBuilder.java b/sechub-adapter/src/main/java/com/mercedesbenz/sechub/adapter/AbstractAdapterConfigBuilder.java index 392669ca59..6a66e98300 100644 --- a/sechub-adapter/src/main/java/com/mercedesbenz/sechub/adapter/AbstractAdapterConfigBuilder.java +++ b/sechub-adapter/src/main/java/com/mercedesbenz/sechub/adapter/AbstractAdapterConfigBuilder.java @@ -12,6 +12,7 @@ import org.slf4j.LoggerFactory; import com.mercedesbenz.sechub.adapter.support.URIShrinkSupport; +import com.mercedesbenz.sechub.commons.core.ConfigurationFailureException; import com.mercedesbenz.sechub.commons.core.security.CryptoAccess; /** @@ -69,10 +70,11 @@ protected URIShrinkSupport createURIShrinkSupport() { * * @param strategy * @return builder (configured by strategy) + * @throws ConfigurationFailureException */ @Override @SuppressWarnings("unchecked") - public final B configure(AdapterConfigurationStrategy strategy) { + public final B configure(AdapterConfigurationStrategy strategy) throws ConfigurationFailureException { strategy.configure((B) this); return (B) this; } diff --git a/sechub-adapter/src/main/java/com/mercedesbenz/sechub/adapter/AdapterConfigBuilder.java b/sechub-adapter/src/main/java/com/mercedesbenz/sechub/adapter/AdapterConfigBuilder.java index 1d4ea473f9..fc450ce2dd 100644 --- a/sechub-adapter/src/main/java/com/mercedesbenz/sechub/adapter/AdapterConfigBuilder.java +++ b/sechub-adapter/src/main/java/com/mercedesbenz/sechub/adapter/AdapterConfigBuilder.java @@ -3,6 +3,8 @@ import static com.mercedesbenz.sechub.adapter.TimeConstants.*; +import com.mercedesbenz.sechub.commons.core.ConfigurationFailureException; + public interface AdapterConfigBuilder { public static final int DEFAULT_SCAN_RESULT_CHECK_IN_MILLISECONDS = TIME_1_MINUTE_IN_MILLISECONDS; @@ -30,7 +32,7 @@ public interface AdapterConfigBuilder { * @param strategy * @return builder (configured by strategy) */ - AdapterConfigBuilder configure(AdapterConfigurationStrategy strategy); + AdapterConfigBuilder configure(AdapterConfigurationStrategy strategy) throws ConfigurationFailureException; /** * Set result check interval in minutes. diff --git a/sechub-adapter/src/main/java/com/mercedesbenz/sechub/adapter/AdapterConfigurationStrategy.java b/sechub-adapter/src/main/java/com/mercedesbenz/sechub/adapter/AdapterConfigurationStrategy.java index b7976bcfae..8f30c4c0a7 100644 --- a/sechub-adapter/src/main/java/com/mercedesbenz/sechub/adapter/AdapterConfigurationStrategy.java +++ b/sechub-adapter/src/main/java/com/mercedesbenz/sechub/adapter/AdapterConfigurationStrategy.java @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.adapter; +import com.mercedesbenz.sechub.commons.core.ConfigurationFailureException; + /** * A configuration strategy is used to configure a given config adapter builder * @@ -16,6 +18,6 @@ public interface AdapterConfigurationStrategy { * * @param configBuilder */ - void configure(B configBuilder); + void configure(B configBuilder) throws ConfigurationFailureException; } diff --git a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/AdministrationAPIConstants.java b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/AdministrationAPIConstants.java index 86b2f04f01..aa31b4545c 100644 --- a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/AdministrationAPIConstants.java +++ b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/AdministrationAPIConstants.java @@ -108,6 +108,12 @@ private AdministrationAPIConstants() { public static final String API_CHANGE_PROJECT_ACCESSLEVEL = API_ADMINISTRATION + "project/{projectId}/accesslevel/{projectAccessLevel}"; + /* +-----------------------------------------------------------------------+ */ + /* +............................ Templates.................................+ */ + /* +-----------------------------------------------------------------------+ */ + public static final String API_ASSIGN_TEMPLATE_TO_PROJECT = API_ADMINISTRATION + "project/{projectId}/template/{templateId}"; + public static final String API_UNASSIGN_TEMPLATE_FROM_PROJECT = API_ADMINISTRATION + "project/{projectId}/template/{templateId}"; + /* +-----------------------------------------------------------------------+ */ /* +............................ Encryption................................+ */ /* +-----------------------------------------------------------------------+ */ diff --git a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/project/Project.java b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/project/Project.java index 890a2c01d3..0faa9b53d5 100644 --- a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/project/Project.java +++ b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/project/Project.java @@ -35,6 +35,7 @@ public class Project { public static final String TABLE_NAME_PROJECT_TO_METADATA = "ADM_PROJECT_TO_METADATA"; public static final String TABLE_NAME_PROJECT_WHITELIST_URI = "ADM_PROJECT_WHITELIST_URI"; public static final String TABLE_NAME_PROJECT_METADATA = "ADM_PROJECT_METADATA"; + public static final String TABLE_NAME_PROJECT_TEMPLATES = "ADM_PROJECT_TEMPLATES"; public static final String COLUMN_PROJECT_ID = "PROJECT_ID"; public static final String COLUMN_PROJECT_OWNER = "PROJECT_OWNER"; @@ -43,10 +44,12 @@ public class Project { public static final String COLUMN_METADATA = "METADATA_KEY"; public static final String COLUMN_PROJECT_ACCESS_LEVEL = "PROJECT_ACCESS_LEVEL"; + public static final String COLUMN_TEMPLATE_ID = "PROJECT_TEMPLATE_ID"; public static final String ASSOCIATE_PROJECT_TO_USER_COLUMN_PROJECT_ID = "PROJECTS_PROJECT_ID"; public static final String ASSOCIATE_PROJECT_TO_URI_COLUMN_PROJECT_ID = "PROJECT_PROJECT_ID"; public static final String ASSOCIATE_PROJECT_TO_METADATA_COLUMN_PROJECT_ID = "PROJECT_ID"; + public static final String ASSOCIATE_PROJECT_TO_TEMPLATE_COLUMN_PROJECT_ID = "PROJECT_PROJECT_ID"; /* +-----------------------------------------------------------------------+ */ /* +............................ JPQL .....................................+ */ @@ -88,6 +91,11 @@ public class Project { @OneToMany(cascade = { CascadeType.REFRESH }, fetch = FetchType.EAGER, mappedBy = ProjectMetaDataEntity.PROPERTY_PROJECT_ID) Set metaData = new HashSet<>(); + @Column(name = COLUMN_TEMPLATE_ID, nullable = false) + @ElementCollection(targetClass = String.class, fetch = FetchType.EAGER) + @CollectionTable(name = TABLE_NAME_PROJECT_TEMPLATES) + Set templateIds = new HashSet<>(); + @Version @Column(name = "VERSION") Integer version; @@ -127,6 +135,10 @@ public ProjectAccessLevel getAccessLevel() { return accessLevel; } + public Set getTemplateIds() { + return templateIds; + } + @Override public int hashCode() { final int prime = 31; diff --git a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/project/ProjectAdministrationRestController.java b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/project/ProjectAdministrationRestController.java index 9b4cd6561b..ad377d6d3c 100644 --- a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/project/ProjectAdministrationRestController.java +++ b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/project/ProjectAdministrationRestController.java @@ -28,6 +28,8 @@ import com.mercedesbenz.sechub.sharedkernel.Step; import com.mercedesbenz.sechub.sharedkernel.project.ProjectAccessLevel; import com.mercedesbenz.sechub.sharedkernel.security.RoleConstants; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminAssignsTemplateToProject; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminUnassignsTemplateFromProject; import com.mercedesbenz.sechub.sharedkernel.usecases.admin.project.UseCaseAdminChangesProjectAccessLevel; import com.mercedesbenz.sechub.sharedkernel.usecases.admin.project.UseCaseAdminChangesProjectDescription; import com.mercedesbenz.sechub.sharedkernel.usecases.admin.project.UseCaseAdminCreatesProject; @@ -86,6 +88,9 @@ public class ProjectAdministrationRestController { @Autowired ListProjectsService listProjectsService; + @Autowired + ProjectTemplateService projectTemplateService; + /* @formatter:off */ @UseCaseAdminCreatesProject( @Step( @@ -180,6 +185,22 @@ public void changeProjectAccessLevel(@PathVariable(name = "projectId") String pr projectAccessLevelChangeService.changeProjectAccessLevel(projectId, projectAccessLevel); } + /* @formatter:off */ + @UseCaseAdminAssignsTemplateToProject(@Step(number = 1, name = "Rest call", description = "Admin does call REST API to assign a template to project", needsRestDoc = true)) + @RequestMapping(path = AdministrationAPIConstants.API_ASSIGN_TEMPLATE_TO_PROJECT, method = RequestMethod.PUT, produces = {MediaType.APPLICATION_JSON_VALUE}) + public void assignTemplateToProject(@PathVariable(name = "projectId") String projectId, @PathVariable(name = "templateId") String templateId) { + /* @formatter:on */ + projectTemplateService.assignTemplateToProject(templateId, projectId); + } + + /* @formatter:off */ + @UseCaseAdminUnassignsTemplateFromProject(@Step(number = 1, name = "Rest call", description = "Admin does call REST API to unassign a template from project", needsRestDoc = true)) + @RequestMapping(path = AdministrationAPIConstants.API_UNASSIGN_TEMPLATE_FROM_PROJECT, method = RequestMethod.DELETE, produces = {MediaType.APPLICATION_JSON_VALUE}) + public void unassignTemplateFromProject(@PathVariable(name = "projectId") String projectId, @PathVariable(name = "templateId") String templateId) { + /* @formatter:on */ + projectTemplateService.unassignTemplateFromProject(templateId, projectId); + } + @InitBinder protected void initBinder(WebDataBinder binder) { binder.setValidator(validator); diff --git a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/project/ProjectDetailInformation.java b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/project/ProjectDetailInformation.java index f33b0c50d1..bdccd79606 100644 --- a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/project/ProjectDetailInformation.java +++ b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/project/ProjectDetailInformation.java @@ -17,16 +17,22 @@ public class ProjectDetailInformation { public static final String PROPERTY_OWNER = "owner"; public static final String PROPERTY_ACCESSLEVEL = "accessLevel"; public static final String PROPERTY_DESCRIPTION = "description"; + public static final String PROPERTY_TEMPLATE_IDS = "templateIds"; private String projectId; private List users = new ArrayList<>(); private List whitelist = new ArrayList<>(); + private List templateIds = new ArrayList<>(); private Map metaData = new HashMap<>(); private String owner; private String description; private String accessLevel; + ProjectDetailInformation() { + /* for JSON */ + } + public ProjectDetailInformation(Project project) { this.projectId = project.getId(); @@ -38,6 +44,8 @@ public ProjectDetailInformation(Project project) { project.getMetaData().forEach(entry -> this.metaData.put(entry.key, entry.value)); + project.getTemplateIds().forEach(templateid -> this.templateIds.add(templateid)); + this.owner = project.getOwner().getName(); this.description = project.getDescription(); @@ -72,4 +80,8 @@ public String getDescription() { public String getAccessLevel() { return accessLevel; } + + public List getTemplateIds() { + return templateIds; + } } diff --git a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/project/ProjectRepositoryImpl.java b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/project/ProjectRepositoryImpl.java index 152164be2b..8b7b7fffe2 100644 --- a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/project/ProjectRepositoryImpl.java +++ b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/project/ProjectRepositoryImpl.java @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.domain.administration.project; +import static com.mercedesbenz.sechub.domain.administration.project.Project.*; + import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.persistence.Query; @@ -9,14 +11,17 @@ public class ProjectRepositoryImpl implements ProjectRepositoryCustom { @PersistenceContext private EntityManager em; +/* @formatter:off */ + private static final String QUERY_DELETE_PROJECT_TO_USER = "delete from " + TABLE_NAME_PROJECT_TO_USER + " p2u where p2u." + ASSOCIATE_PROJECT_TO_USER_COLUMN_PROJECT_ID + " = ?1"; + + private static final String QUERY_DELETE_PROJECT_TO_URI = "delete from " + TABLE_NAME_PROJECT_WHITELIST_URI + " p2w where p2w." + Project.ASSOCIATE_PROJECT_TO_URI_COLUMN_PROJECT_ID + " = ?1"; + + private static final String QUERY_DELETE_PROJECT_TO_METADATA = "delete from " + TABLE_NAME_PROJECT_METADATA + " p2w where p2w." + Project.ASSOCIATE_PROJECT_TO_METADATA_COLUMN_PROJECT_ID + " = ?1"; - private static final String QUERY_DELETE_PROJECT_TO_USER = "delete from " + Project.TABLE_NAME_PROJECT_TO_USER + " p2u where p2u." - + Project.ASSOCIATE_PROJECT_TO_USER_COLUMN_PROJECT_ID + " = ?1"; - private static final String QUERY_DELETE_PROJECT_TO_URI = "delete from " + Project.TABLE_NAME_PROJECT_WHITELIST_URI + " p2w where p2w." - + Project.ASSOCIATE_PROJECT_TO_URI_COLUMN_PROJECT_ID + " = ?1"; - private static final String QUERY_DELETE_PROJECT_TO_METADATA = "delete from " + Project.TABLE_NAME_PROJECT_METADATA + " p2w where p2w." - + Project.ASSOCIATE_PROJECT_TO_METADATA_COLUMN_PROJECT_ID + " = ?1"; - private static final String QUERY_DELETE_PROJECT = "delete from " + Project.TABLE_NAME + " p where p." + Project.COLUMN_PROJECT_ID + " = ?1"; + private static final String QUERY_DELETE_PROJECT_TO_TEMPLATE = "delete from " + TABLE_NAME_PROJECT_TEMPLATES + " p2w where p2w." + Project.ASSOCIATE_PROJECT_TO_TEMPLATE_COLUMN_PROJECT_ID + " = ?1"; + + private static final String QUERY_DELETE_PROJECT = "delete from " + TABLE_NAME + " p where p." + Project.COLUMN_PROJECT_ID + " = ?1"; + /* @formatter:on */ @Override public void deleteProjectWithAssociations(String projectId) { @@ -32,6 +37,10 @@ public void deleteProjectWithAssociations(String projectId) { deleteProjectToMetaData.setParameter(1, projectId); deleteProjectToMetaData.executeUpdate(); + Query deleteProjectToTemplate = em.createNativeQuery(QUERY_DELETE_PROJECT_TO_TEMPLATE); + deleteProjectToTemplate.setParameter(1, projectId); + deleteProjectToTemplate.executeUpdate(); + Query deleteProject = em.createNativeQuery(QUERY_DELETE_PROJECT); deleteProject.setParameter(1, projectId); deleteProject.executeUpdate(); diff --git a/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/project/ProjectTemplateService.java b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/project/ProjectTemplateService.java new file mode 100644 index 0000000000..d930cdf142 --- /dev/null +++ b/sechub-administration/src/main/java/com/mercedesbenz/sechub/domain/administration/project/ProjectTemplateService.java @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.administration.project; + +import java.util.List; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.mercedesbenz.sechub.sharedkernel.Step; +import com.mercedesbenz.sechub.sharedkernel.error.NotAcceptableException; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessage; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessageService; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessageSynchronousResult; +import com.mercedesbenz.sechub.sharedkernel.messaging.IsSendingSyncMessage; +import com.mercedesbenz.sechub.sharedkernel.messaging.MessageDataKeys; +import com.mercedesbenz.sechub.sharedkernel.messaging.MessageID; +import com.mercedesbenz.sechub.sharedkernel.security.RoleConstants; +import com.mercedesbenz.sechub.sharedkernel.template.SecHubProjectTemplateData; +import com.mercedesbenz.sechub.sharedkernel.template.SecHubProjectToTemplate; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminAssignsTemplateToProject; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminUnassignsTemplateFromProject; +import com.mercedesbenz.sechub.sharedkernel.validation.UserInputAssertion; + +import jakarta.annotation.security.RolesAllowed; + +@Service +@RolesAllowed(RoleConstants.ROLE_SUPERADMIN) +public class ProjectTemplateService { + + private static final Logger LOG = LoggerFactory.getLogger(ProjectTemplateService.class); + + @Autowired + DomainMessageService eventBus; + + @Autowired + ProjectRepository projectRepository; + + @Autowired + ProjectTransactionService projectTansactionService; + + @Autowired + UserInputAssertion assertion; + + /* @formatter:off */ + @UseCaseAdminAssignsTemplateToProject( + @Step( + number = 2, + name = "service assigns template to project", + description = "The service will request the template assignment in domain 'scan' via synchronous event and updates mapping in domain 'administration' afterwards")) + /* @formatter:on */ + public void assignTemplateToProject(String templateId, String projectId) { + changeTemplateAssignment(templateId, projectId, (t, p) -> fetchAssignRequestResult(t, p), "assigned to"); + } + + /* @formatter:off */ + @UseCaseAdminUnassignsTemplateFromProject( + @Step( + number = 2, + name = "service unassigns template from project", + description = "The service will request the template unassignment in domain 'scan' via synchronous event and updates mapping in domain 'administration' afterwards")) + /* @formatter:on */ + public void unassignTemplateFromProject(String templateId, String projectId) { + changeTemplateAssignment(templateId, projectId, (t, p) -> fetchUnassignmentRequestResult(t, p), "unassigned from"); + } + + private void changeTemplateAssignment(String templateId, String projectId, TemplateChangeResultFetcher fetcher, String assignOrUnassignInfo) { + assertion.assertIsValidTemplateId(templateId); + assertion.assertIsValidProjectId(projectId); + + Project project = projectRepository.findOrFailProject(projectId); + Set templateIds = project.getTemplateIds(); + LOG.debug("Project '{}' has following template ids: {}", projectId, templateIds); + + SecHubProjectTemplateData result = fetcher.fetchTemplateAssignmentChangeResult(templateId, projectId); + List newTemplateIds = result.getTemplateIds(); + templateIds.clear(); + templateIds.addAll(newTemplateIds); + + projectTansactionService.saveInOwnTransaction(project); + LOG.info("Template '{}' has been {} project '{}'", templateId, assignOrUnassignInfo, projectId); + + LOG.debug("Project '{}' has following template ids: {}", projectId, templateIds); + } + + @IsSendingSyncMessage(MessageID.REQUEST_ASSIGN_TEMPLATE_TO_PROJECT) + private SecHubProjectTemplateData fetchAssignRequestResult(String templateId, String projectId) { + return sendSynchronousProjectTemplateChangeEvent(templateId, projectId, MessageID.REQUEST_ASSIGN_TEMPLATE_TO_PROJECT, + MessageID.RESULT_ASSIGN_TEMPLATE_TO_PROJECT); + + } + + @IsSendingSyncMessage(MessageID.REQUEST_UNASSIGN_TEMPLATE_FROM_PROJECT) + private SecHubProjectTemplateData fetchUnassignmentRequestResult(String templateId, String projectId) { + return sendSynchronousProjectTemplateChangeEvent(templateId, projectId, MessageID.REQUEST_UNASSIGN_TEMPLATE_FROM_PROJECT, + MessageID.RESULT_UNASSIGN_TEMPLATE_FROM_PROJECT); + } + + /* + * This method sends a synchronous event to event bus and waits that the + * assignment is done inside other domain (in this case we know it is inside the + * scan domain). When this is done, the change event was successful and the + * event result contains SecHubProjectTemplateData which can be used inside + * administration domain further. + */ + private SecHubProjectTemplateData sendSynchronousProjectTemplateChangeEvent(String templateId, String projectId, MessageID requestMessageId, + MessageID acceptedResultMessageId) { + + DomainMessage message = new DomainMessage(requestMessageId); + + SecHubProjectToTemplate mapping = new SecHubProjectToTemplate(); + mapping.setProjectId(projectId); + mapping.setTemplateId(templateId); + message.set(MessageDataKeys.PROJECT_TO_TEMPLATE, mapping); + + DomainMessageSynchronousResult result = eventBus.sendSynchron(message); + + if (result.hasFailed()) { + throw new NotAcceptableException("Was not able to change template to project assignment.\nReason: " + result.getErrorMessage()); + } + MessageID messageID = result.getMessageId(); + if (!(acceptedResultMessageId.equals(messageID))) { + throw new IllegalStateException("Result message id not supported: " + messageID); + } + return result.get(MessageDataKeys.PROJECT_TEMPLATES); + } + + private interface TemplateChangeResultFetcher { + public SecHubProjectTemplateData fetchTemplateAssignmentChangeResult(String templateId, String projectId); + } +} diff --git a/sechub-administration/src/test/java/com/mercedesbenz/sechub/domain/administration/project/ProjectAdministrationRestControllerMockTest.java b/sechub-administration/src/test/java/com/mercedesbenz/sechub/domain/administration/project/ProjectAdministrationRestControllerMockTest.java index eff11c36a9..1a117259a6 100644 --- a/sechub-administration/src/test/java/com/mercedesbenz/sechub/domain/administration/project/ProjectAdministrationRestControllerMockTest.java +++ b/sechub-administration/src/test/java/com/mercedesbenz/sechub/domain/administration/project/ProjectAdministrationRestControllerMockTest.java @@ -96,6 +96,9 @@ public class ProjectAdministrationRestControllerMockTest { @MockBean ProjectChangeAccessLevelService projectChangeAccessLevelService; + @MockBean + ProjectTemplateService projectTemplateService; + @Before public void before() { when(createProjectInputvalidator.supports(ProjectJsonInput.class)).thenReturn(true); diff --git a/sechub-administration/src/test/java/com/mercedesbenz/sechub/domain/administration/project/ProjectDetailInformationTest.java b/sechub-administration/src/test/java/com/mercedesbenz/sechub/domain/administration/project/ProjectDetailInformationTest.java new file mode 100644 index 0000000000..62fc101608 --- /dev/null +++ b/sechub-administration/src/test/java/com/mercedesbenz/sechub/domain/administration/project/ProjectDetailInformationTest.java @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.administration.project; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.net.URI; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import com.mercedesbenz.sechub.domain.administration.user.User; +import com.mercedesbenz.sechub.sharedkernel.project.ProjectAccessLevel; + +class ProjectDetailInformationTest { + + @Test + void constructor_stores_data_from_project_into_fields() { + /* prepare */ + Project project = mock(Project.class); + + User owner = mock(User.class); + when(owner.getName()).thenReturn("owner1"); + + User user1 = mock(User.class); + when(user1.getName()).thenReturn("user1"); + + User user2 = mock(User.class); + when(user2.getName()).thenReturn("user2"); + + String projectId = "id1"; + ProjectMetaDataEntity metaDataEntity = new ProjectMetaDataEntity(projectId, "key1", "value1"); + ProjectAccessLevel accessLevel = ProjectAccessLevel.FULL; + + when(project.getWhiteList()).thenReturn(Set.of(URI.create("https://example.com"))); + when(project.getAccessLevel()).thenReturn(accessLevel); + when(project.getId()).thenReturn(projectId); + when(project.getOwner()).thenReturn(owner); + when(project.getMetaData()).thenReturn(Set.of(metaDataEntity)); + when(project.getUsers()).thenReturn(Set.of(user1, user2)); + when(project.getTemplateIds()).thenReturn(Set.of("template1", "template2")); + + /* execute */ + ProjectDetailInformation toTest = new ProjectDetailInformation(project); + + /* test */ + assertThat(toTest.getProjectId()).isEqualTo(projectId); + assertThat(toTest.getAccessLevel()).isEqualTo(accessLevel.getId()); + assertThat(toTest.getOwner()).isEqualTo("owner1"); + assertThat(toTest.getUsers()).contains("user1", "user2"); + assertThat(toTest.getWhiteList()).contains("https://example.com"); + assertThat(toTest.getTemplateIds()).contains("template1", "template2"); + assertThat(toTest.getMetaData()).containsEntry("key1", "value1"); + + } + +} diff --git a/sechub-administration/src/test/java/com/mercedesbenz/sechub/domain/administration/project/ProjectTemplateServiceTest.java b/sechub-administration/src/test/java/com/mercedesbenz/sechub/domain/administration/project/ProjectTemplateServiceTest.java new file mode 100644 index 0000000000..dd9ea02985 --- /dev/null +++ b/sechub-administration/src/test/java/com/mercedesbenz/sechub/domain/administration/project/ProjectTemplateServiceTest.java @@ -0,0 +1,202 @@ +package com.mercedesbenz.sechub.domain.administration.project; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; +import org.mockito.ArgumentCaptor; + +import com.mercedesbenz.sechub.sharedkernel.error.NotAcceptableException; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessage; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessageService; +import com.mercedesbenz.sechub.sharedkernel.messaging.DomainMessageSynchronousResult; +import com.mercedesbenz.sechub.sharedkernel.messaging.MessageDataKeys; +import com.mercedesbenz.sechub.sharedkernel.messaging.MessageID; +import com.mercedesbenz.sechub.sharedkernel.template.SecHubProjectTemplateData; +import com.mercedesbenz.sechub.sharedkernel.template.SecHubProjectToTemplate; + +class ProjectTemplateServiceTest { + + private static final String CORRECT_ASSIGN_TEMPLATE_RESULT_MESSAGE_ID = "RESULT_ASSIGN_TEMPLATE_TO_PROJECT"; + private static final String CORRECT_UNASSIGN_TEMPLATE_RESULT_MESSAGE_ID = "RESULT_UNASSIGN_TEMPLATE_FROM_PROJECT"; + private static final String ASSIGNED_TEMPLATE_ID_A = "template-a"; + private static final String ASSIGNED_TEMPLATE_ID_B = "template-b"; + + private static final String ASSIGNED_TEMPLATE_ID_AFTER_CHANGE_1 = "template-after_change-1"; + private static final String ASSIGNED_TEMPLATE_ID_AFTER_CHANGE_2 = "template-after-change-2"; + + private static final String PROJECT_ID1 = "project1"; + private static final String TEMPLATE_ID1 = "templateId1"; + private ProjectTemplateService serviceToTest; + private DomainMessageService eventBus; + private ProjectRepository projectRepository; + private ProjectTransactionService projectTansactionService; + + @BeforeEach + void beforeEach() { + + eventBus = mock(); + projectRepository = mock(); + projectTansactionService = mock(); + + serviceToTest = new ProjectTemplateService(); + serviceToTest.assertion = mock(); + serviceToTest.eventBus = eventBus; + + serviceToTest.projectRepository = projectRepository; + serviceToTest.projectTansactionService = projectTansactionService; + } + + @Test + void assignTemplateToProject_sends_assign_request_synchronous_with_expected_data() { + + /* prepare */ + mockEventBusSynchronResultWithMessageId(MessageID.RESULT_ASSIGN_TEMPLATE_TO_PROJECT); + + /* execute */ + serviceToTest.assignTemplateToProject(TEMPLATE_ID1, PROJECT_ID1); + + /* test */ + ArgumentCaptor messageCaptor = ArgumentCaptor.captor(); + verify(eventBus).sendSynchron(messageCaptor.capture()); + + DomainMessage sentMessage = messageCaptor.getValue(); + assertThat(sentMessage).isNotNull(); + assertThat(sentMessage.getMessageId()).isEqualTo(MessageID.REQUEST_ASSIGN_TEMPLATE_TO_PROJECT); + SecHubProjectToTemplate sentMessageData = sentMessage.get(MessageDataKeys.PROJECT_TO_TEMPLATE); + assertThat(sentMessageData).isNotNull(); + assertThat(sentMessageData.getProjectId()).isEqualTo(PROJECT_ID1); + assertThat(sentMessageData.getTemplateId()).isEqualTo(TEMPLATE_ID1); + + } + + @Test + void assignTemplateToProject_updates_template_by_synchronous_event_result() { + + /* prepare */ + mockEventBusSynchronResultWithMessageId(MessageID.RESULT_ASSIGN_TEMPLATE_TO_PROJECT); + + /* execute */ + serviceToTest.assignTemplateToProject(TEMPLATE_ID1, PROJECT_ID1); + + /* test */ + ArgumentCaptor projectCaptor = ArgumentCaptor.forClass(Project.class); + verify(projectTansactionService).saveInOwnTransaction(projectCaptor.capture()); + Project projectSaved = projectCaptor.getValue(); + assertThat(projectSaved.getTemplateIds()).hasSize(2).describedAs("project templates must be changed by result data") + .contains(ASSIGNED_TEMPLATE_ID_AFTER_CHANGE_1).contains(ASSIGNED_TEMPLATE_ID_AFTER_CHANGE_2); + + } + + @Test + void unassignTemplateFromProject_sends_assign_request_synchronous_with_expected_data() { + + /* prepare */ + mockEventBusSynchronResultWithMessageId(MessageID.RESULT_UNASSIGN_TEMPLATE_FROM_PROJECT); + + /* execute */ + serviceToTest.unassignTemplateFromProject(TEMPLATE_ID1, PROJECT_ID1); + + /* test */ + ArgumentCaptor messageCaptor = ArgumentCaptor.captor(); + verify(eventBus).sendSynchron(messageCaptor.capture()); + + DomainMessage sentMessage = messageCaptor.getValue(); + assertThat(sentMessage).isNotNull(); + assertThat(sentMessage.getMessageId()).isEqualTo(MessageID.REQUEST_UNASSIGN_TEMPLATE_FROM_PROJECT); + SecHubProjectToTemplate sentMessageData = sentMessage.get(MessageDataKeys.PROJECT_TO_TEMPLATE); + assertThat(sentMessageData).isNotNull(); + assertThat(sentMessageData.getProjectId()).isEqualTo(PROJECT_ID1); + assertThat(sentMessageData.getTemplateId()).isEqualTo(TEMPLATE_ID1); + } + + @Test + void unassignTemplateFromProject_updates_template_by_synchronous_event_result() { + + /* prepare */ + mockEventBusSynchronResultWithMessageId(MessageID.RESULT_UNASSIGN_TEMPLATE_FROM_PROJECT); + + /* execute */ + serviceToTest.unassignTemplateFromProject(TEMPLATE_ID1, PROJECT_ID1); + + /* test */ + ArgumentCaptor projectCaptor = ArgumentCaptor.forClass(Project.class); + verify(projectTansactionService).saveInOwnTransaction(projectCaptor.capture()); + Project projectSaved = projectCaptor.getValue(); + assertThat(projectSaved.getTemplateIds()).hasSize(2).describedAs("project templates must be changed by result data") + .contains(ASSIGNED_TEMPLATE_ID_AFTER_CHANGE_1).contains(ASSIGNED_TEMPLATE_ID_AFTER_CHANGE_2); + + } + + @ParameterizedTest + @EnumSource(value = MessageID.class, mode = Mode.EXCLUDE, names = CORRECT_UNASSIGN_TEMPLATE_RESULT_MESSAGE_ID) + void unassignTemplateFromProject_when_synchronous_event_result_has_unsupported_message_throws_invalid_exception(MessageID wrongMessageId) { + /* prepare */ + mockEventBusSynchronResultWithMessageId(wrongMessageId); + + /* execute + test */ + assertThatThrownBy(() -> serviceToTest.unassignTemplateFromProject(TEMPLATE_ID1, PROJECT_ID1)).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Result message id not supported"); + + } + + @ParameterizedTest + @EnumSource(value = MessageID.class, mode = Mode.EXCLUDE, names = CORRECT_ASSIGN_TEMPLATE_RESULT_MESSAGE_ID) + void assignTemplateToProject_when_synchronous_event_result_has_unsupported_message_throws_invalid_exception(MessageID wrongMessageId) { + mockEventBusSynchronResultWithMessageId(wrongMessageId); + + /* execute + test */ + assertThatThrownBy(() -> serviceToTest.assignTemplateToProject(TEMPLATE_ID1, PROJECT_ID1)).isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Result message id not supported"); + + } + + @Test + void assignTemplateToProject_when_event_result_failed_exception_is_thrown() { + /* prepare */ + DomainMessageSynchronousResult mockedResultMessage = mockEventBusSynchronResultWithMessageId( + MessageID.valueOf(CORRECT_UNASSIGN_TEMPLATE_RESULT_MESSAGE_ID)); + when(mockedResultMessage.hasFailed()).thenReturn(true); + + assertThatThrownBy(() -> serviceToTest.assignTemplateToProject(TEMPLATE_ID1, PROJECT_ID1)).isInstanceOf(NotAcceptableException.class) + .hasMessageContaining("Was not able to change template to project assignment"); + } + + @Test + void unassignTemplateFromProject_when_event_result_failed_exception_is_thrown() { + /* prepare */ + DomainMessageSynchronousResult mockedResultMessage = mockEventBusSynchronResultWithMessageId( + MessageID.valueOf(CORRECT_UNASSIGN_TEMPLATE_RESULT_MESSAGE_ID)); + when(mockedResultMessage.hasFailed()).thenReturn(true); + + assertThatThrownBy(() -> serviceToTest.unassignTemplateFromProject(TEMPLATE_ID1, PROJECT_ID1)).isInstanceOf(NotAcceptableException.class) + .hasMessageContaining("Was not able to change template to project assignment"); + } + + private DomainMessageSynchronousResult mockEventBusSynchronResultWithMessageId(MessageID resultMessageId) { + Project project1 = new Project(); + project1.getTemplateIds().addAll(Set.of(ASSIGNED_TEMPLATE_ID_A, ASSIGNED_TEMPLATE_ID_B)); + + when(projectRepository.findOrFailProject(PROJECT_ID1)).thenReturn(project1); + + SecHubProjectTemplateData mockedResultData = mock(); + when(mockedResultData.getProjectId()).thenReturn("result-project"); + when(mockedResultData.getTemplateIds()).thenReturn(List.of(ASSIGNED_TEMPLATE_ID_AFTER_CHANGE_1, ASSIGNED_TEMPLATE_ID_AFTER_CHANGE_2)); + DomainMessageSynchronousResult mockedResultMessage = mock(); + when(mockedResultMessage.getMessageId()).thenReturn(resultMessageId); + when(mockedResultMessage.get(MessageDataKeys.PROJECT_TEMPLATES)).thenReturn(mockedResultData); + + when(eventBus.sendSynchron(any(DomainMessage.class))).thenReturn(mockedResultMessage); + + return mockedResultMessage; + } + +} diff --git a/sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/Template.java b/sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/CodeTemplate.java similarity index 92% rename from sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/Template.java rename to sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/CodeTemplate.java index 1b115771d3..68e181d237 100644 --- a/sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/Template.java +++ b/sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/CodeTemplate.java @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.api.generator; -class Template { +class CodeTemplate { StringBuilder sb = new StringBuilder(); String getCode() { diff --git a/sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/InternalAccessModelFileGenerator.java b/sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/InternalAccessModelFileGenerator.java index f45a5c1bdb..fe433832eb 100644 --- a/sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/InternalAccessModelFileGenerator.java +++ b/sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/InternalAccessModelFileGenerator.java @@ -41,7 +41,7 @@ private void generateAbstractModel(MapGenInfo info) throws Exception { LOG.debug("To : {}", genFile); LOG.debug(""); - Template template = new Template(); + CodeTemplate template = new CodeTemplate(); template.addLine("// SPDX-License-Identifier: MIT"); template.addLine("package " + context.getTargetAbstractModelPackage() + ";"); template.addLine(""); @@ -102,7 +102,7 @@ private void generateAbstractModel(MapGenInfo info) throws Exception { } - private void generateMethods(Template template, List methods) { + private void generateMethods(CodeTemplate template, List methods) { for (Method method : methods) { context.getSetterGetterSupport().generateMethod(method, template, "public", true, "delegate"); } diff --git a/sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/PublicModelFileGenerator.java b/sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/PublicModelFileGenerator.java index d22bdd8f89..907964c44a 100644 --- a/sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/PublicModelFileGenerator.java +++ b/sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/PublicModelFileGenerator.java @@ -46,7 +46,7 @@ private void generatetPublicModel(MapGenInfo info, boolean overwritePublicModelF String internalAccessClass = context.getTargetAbstractModelPackage() + "." + info.targetInternalAccessClassName; - Template template = new Template(); + CodeTemplate template = new CodeTemplate(); template.addLine("// SPDX-License-Identifier: MIT"); template.addLine("package " + context.getTargetModelPackage() + ";"); template.addLine(""); @@ -114,13 +114,13 @@ private void generatetPublicModel(MapGenInfo info, boolean overwritePublicModelF } - private void generatePublicSetterGetterMethods(MapGenInfo info, Template template) { + private void generatePublicSetterGetterMethods(MapGenInfo info, CodeTemplate template) { for (Method method : collectGettersAndSetters(info.fromGenclazz)) { context.getSetterGetterSupport().generateMethod(method, template, "public", false, "internalAccess"); } } - private void generateAdditionalWrapperMethods(MapGenInfo info, Template template) { + private void generateAdditionalWrapperMethods(MapGenInfo info, CodeTemplate template) { Map map = info.getReferenceMap(); for (String beanName : map.keySet()) { @@ -130,7 +130,7 @@ private void generateAdditionalWrapperMethods(MapGenInfo info, Template template } - private void generateMethodsToReferenceOtherWrapper(String beanName, BeanDataContainer other, Template template) { + private void generateMethodsToReferenceOtherWrapper(String beanName, BeanDataContainer other, CodeTemplate template) { String fieldName = asFieldName(beanName); if (other.isAsList()) { @@ -168,7 +168,7 @@ private void generateMethodsToReferenceOtherWrapper(String beanName, BeanDataCon } } - private void generateAdditionalWrapperFields(MapGenInfo info, Template template) { + private void generateAdditionalWrapperFields(MapGenInfo info, CodeTemplate template) { Map map = info.getReferenceMap(); for (String beanName : map.keySet()) { @@ -178,7 +178,7 @@ private void generateAdditionalWrapperFields(MapGenInfo info, Template template) } - private void generateAdditionalWrapperFieldsForOther(String beanName, BeanDataContainer other, Template template) { + private void generateAdditionalWrapperFieldsForOther(String beanName, BeanDataContainer other, CodeTemplate template) { template.addLine(" private " + other.asTargetTypeResult() + " " + asFieldName(beanName) + ";"); } } diff --git a/sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/SetterGetterGenerationSupport.java b/sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/SetterGetterGenerationSupport.java index 7baed8d71b..c6a14bd5e9 100644 --- a/sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/SetterGetterGenerationSupport.java +++ b/sechub-api-java/src/test/java/com/mercedesbenz/sechub/api/generator/SetterGetterGenerationSupport.java @@ -17,7 +17,7 @@ public SetterGetterGenerationSupport(ApiWrapperGenerationContext context) { this.context = context; } - public void generateMethod(Method method, Template template, String visibility, boolean handleNull, String callFieldName) { + public void generateMethod(Method method, CodeTemplate template, String visibility, boolean handleNull, String callFieldName) { List paramList = getParameters(method); String methodSignature = createMethodSignature(method, paramList, visibility); diff --git a/sechub-commons-archive/src/main/java/com/mercedesbenz/sechub/commons/archive/ArchiveSupport.java b/sechub-commons-archive/src/main/java/com/mercedesbenz/sechub/commons/archive/ArchiveSupport.java index e03c99e287..a57352dc9f 100644 --- a/sechub-commons-archive/src/main/java/com/mercedesbenz/sechub/commons/archive/ArchiveSupport.java +++ b/sechub-commons-archive/src/main/java/com/mercedesbenz/sechub/commons/archive/ArchiveSupport.java @@ -4,6 +4,7 @@ import static java.util.Objects.requireNonNull; import java.io.File; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; @@ -329,6 +330,28 @@ public void compressFolder(ArchiveType type, File folder, File targetArchiveFile } + /** + * Just extracts the given archive file to target folder, without any + * adjustments. + * + * @param archiveType archive type to use + * @param archiveFile file which shall be extracted + * @param targetFolder target folder where extraction shall be done + * @throws IOException + */ + public ArchiveExtractionResult extractFileAsIsToFolder(ArchiveType archiveType, File archiveFile, File targetFolder, + ArchiveExtractionConstraints archiveExtractionConstraints) throws IOException { + try (FileInputStream sourceInputStream = new FileInputStream(archiveFile)) { + String sourceLocation = archiveFile.getAbsolutePath(); + var result = switch (archiveType) { + case TAR -> extractTar(sourceInputStream, sourceLocation, targetFolder, null, archiveExtractionConstraints); + case ZIP -> extractZip(sourceInputStream, sourceLocation, targetFolder, null, archiveExtractionConstraints); + default -> throw new IllegalArgumentException("Archive type: " + archiveType + " is not supported!"); + }; + return result; + } + } + private void compressRecursively(String basePath, ArchiveOutputStream outputStream, File file, ArchiveType type, String pathAddition, CreationPathContext creationPathContext) throws IOException { @@ -407,13 +430,13 @@ private void compressRecursively(String basePath, ArchiveOutputStream outputStre } private ArchiveExtractionResult extractTar(InputStream sourceInputStream, String sourceLocation, File outputDir, - SecHubFileStructureDataProvider fileStructureProvider, ArchiveExtractionConstraints archiveExtractionConstraints) throws IOException { + SecHubFileStructureDataProvider fileStructurDataProvider, ArchiveExtractionConstraints archiveExtractionConstraints) throws IOException { try (ArchiveInputStream archiveInputStream = new ArchiveStreamFactory().createArchiveInputStream(ArchiveType.TAR.getType(), sourceInputStream)) { if (!(archiveInputStream instanceof TarArchiveInputStream)) { throw new IOException("Cannot extract: " + sourceLocation + " because it is not a tar tar"); } try (SafeArchiveInputStream safeArchiveInputStream = new SafeArchiveInputStream(archiveInputStream, archiveExtractionConstraints)) { - return extract(safeArchiveInputStream, sourceLocation, outputDir, fileStructureProvider); + return extract(safeArchiveInputStream, sourceLocation, outputDir, fileStructurDataProvider); } } catch (ArchiveException e) { throw new IOException("Was not able to extract tar:" + sourceLocation + " at " + outputDir, e); @@ -422,11 +445,11 @@ private ArchiveExtractionResult extractTar(InputStream sourceInputStream, String } private ArchiveExtractionResult extractZip(InputStream sourceInputStream, String sourceLocation, File outputDir, - SecHubFileStructureDataProvider configuration, ArchiveExtractionConstraints archiveExtractionConstraints) throws IOException { + SecHubFileStructureDataProvider fileStructurDataProvider, ArchiveExtractionConstraints archiveExtractionConstraints) throws IOException { try (ArchiveInputStream archiveInputStream = new ArchiveStreamFactory().createArchiveInputStream(ArchiveType.ZIP.getType(), sourceInputStream); SafeArchiveInputStream safeArchiveInputStream = new SafeArchiveInputStream(archiveInputStream, archiveExtractionConstraints)) { - return extract(safeArchiveInputStream, sourceLocation, outputDir, configuration); + return extract(safeArchiveInputStream, sourceLocation, outputDir, fileStructurDataProvider); } catch (ArchiveException e) { throw new IOException("Was not able to extract tar:" + sourceLocation + " at " + outputDir, e); @@ -460,7 +483,7 @@ public boolean isZipFileStream(InputStream inputStream) { } private ArchiveExtractionResult extract(SafeArchiveInputStream safeArchiveInputStream, String sourceLocation, File outputDir, - SecHubFileStructureDataProvider fileStructureProvider) throws ArchiveException, IOException { + SecHubFileStructureDataProvider fileStructurDataProvider) throws ArchiveException, IOException { ArchiveExtractionResult result = new ArchiveExtractionResult(); result.targetLocation = outputDir.getAbsolutePath(); @@ -473,7 +496,7 @@ private ArchiveExtractionResult extract(SafeArchiveInputStream safeArchiveInputS throw new IllegalStateException("Entry path is null - cannot be handled!"); } - ArchiveTransformationData data = createTransformationData(fileStructureProvider, name); + ArchiveTransformationData data = createTransformationData(fileStructurDataProvider, name); if (data == null) { continue; } diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/ConfigurationFailureException.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/ConfigurationFailureException.java new file mode 100644 index 0000000000..c089e29f3d --- /dev/null +++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/ConfigurationFailureException.java @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.core; + +public class ConfigurationFailureException extends Exception { + + private static final long serialVersionUID = -384180667154600386L; + + public ConfigurationFailureException(String message) { + super(message); + } + + public ConfigurationFailureException(String message, Throwable cause) { + super(message, cause); + } + +} \ No newline at end of file diff --git a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/util/SecHubStorageUtil.java b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/util/SecHubStorageUtil.java index 988636685c..6279b686c0 100644 --- a/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/util/SecHubStorageUtil.java +++ b/sechub-commons-core/src/main/java/com/mercedesbenz/sechub/commons/core/util/SecHubStorageUtil.java @@ -13,4 +13,8 @@ public class SecHubStorageUtil { public static String createStoragePathForProject(String projectId) { return "jobstorage/" + projectId; } + + public static String createAssetStoragePath() { + return "assets"; + } } diff --git a/sechub-commons-model/build.gradle b/sechub-commons-model/build.gradle index f0e7b9d98b..e8b8741516 100644 --- a/sechub-commons-model/build.gradle +++ b/sechub-commons-model/build.gradle @@ -17,4 +17,5 @@ dependencies{ testImplementation spring_boot_dependency.mockito_core testImplementation spring_boot_dependency.hamcrest + testImplementation spring_boot_dependency.assertj_core } \ No newline at end of file diff --git a/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/login/WebLoginConfiguration.java b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/login/WebLoginConfiguration.java index 3284dd9875..2809810f41 100644 --- a/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/login/WebLoginConfiguration.java +++ b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/login/WebLoginConfiguration.java @@ -5,6 +5,7 @@ import java.util.Optional; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.mercedesbenz.sechub.commons.model.template.TemplateData; @JsonIgnoreProperties(ignoreUnknown = true) public class WebLoginConfiguration { @@ -19,6 +20,8 @@ public class WebLoginConfiguration { private WebLoginTOTPConfiguration totp; + private TemplateData templateData; + public URL getUrl() { return url; } @@ -43,4 +46,12 @@ public void setTotp(WebLoginTOTPConfiguration totp) { this.totp = totp; } + public TemplateData getTemplateData() { + return templateData; + } + + public void setTemplateData(TemplateData templateData) { + this.templateData = templateData; + } + } \ No newline at end of file diff --git a/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateData.java b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateData.java new file mode 100644 index 0000000000..a0f2216ef4 --- /dev/null +++ b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateData.java @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.model.template; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Template data for SecHub configuration model. Here users can define user + * specific template data - e.g. variables like "username", "password" + * + * @author Albert Tregnaghi + * + */ +public class TemplateData { + + private Map variables = new LinkedHashMap<>(); + + public Map getVariables() { + return variables; + } + +} diff --git a/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateDataResolver.java b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateDataResolver.java new file mode 100644 index 0000000000..7da6386f33 --- /dev/null +++ b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateDataResolver.java @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.model.template; + +import java.util.Optional; + +import com.mercedesbenz.sechub.commons.model.SecHubConfigurationModel; +import com.mercedesbenz.sechub.commons.model.SecHubWebScanConfiguration; +import com.mercedesbenz.sechub.commons.model.login.WebLoginConfiguration; + +public class TemplateDataResolver { + + public TemplateData resolveTemplateData(TemplateType type, SecHubConfigurationModel configuration) { + if (type == null) { + return null; + } + if (configuration == null) { + return null; + } + switch (type) { + case WEBSCAN_LOGIN: + return resolveWebScanLoginTemplateData(configuration); + default: + break; + } + return null; + } + + private TemplateData resolveWebScanLoginTemplateData(SecHubConfigurationModel configuration) { + Optional webScanOpt = configuration.getWebScan(); + if (webScanOpt.isEmpty()) { + return null; + } + SecHubWebScanConfiguration webScan = webScanOpt.get(); + Optional loginOpt = webScan.getLogin(); + if (loginOpt.isEmpty()) { + return null; + } + WebLoginConfiguration login = loginOpt.get(); + return login.getTemplateData(); + + } +} diff --git a/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateDefinition.java b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateDefinition.java new file mode 100644 index 0000000000..b8296dbb29 --- /dev/null +++ b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateDefinition.java @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.model.template; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.mercedesbenz.sechub.commons.model.JSONable; + +@JsonPropertyOrder({ "id", "type", "variables", "assets" }) +public class TemplateDefinition implements JSONable { + + private static TemplateDefinition IMPORTER = new TemplateDefinition(); + + public static final String PROPERTY_TYPE = "type"; + public static final String PROPERTY_ID = "id"; + public static final String PROPERTY_ASSET_ID = "assetId"; + public static final String PROPERTY_VARIABLES = "variables"; + + private TemplateType type; + + private String assetId; + private List variables = new ArrayList<>(); + + private String id; + + public TemplateDefinition() { + } + + public static TemplateDefinitionBuilder builder() { + return new TemplateDefinitionBuilder(); + } + + public static TemplateDefinition from(String json) { + return IMPORTER.fromJSON(json); + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setAssetId(String assetId) { + this.assetId = assetId; + } + + public String getAssetId() { + return assetId; + } + + public List getVariables() { + return variables; + } + + public void setType(TemplateType type) { + this.type = type; + } + + public TemplateType getType() { + return type; + } + + @Override + public Class getJSONTargetClass() { + return TemplateDefinition.class; + } + + @Override + public int hashCode() { + return Objects.hash(assetId, id, type, variables); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TemplateDefinition other = (TemplateDefinition) obj; + return Objects.equals(assetId, other.assetId) && Objects.equals(id, other.id) && type == other.type && Objects.equals(variables, other.variables); + } + + public static class TemplateDefinitionBuilder { + + private String assetId; + private String templateId; + private TemplateType templateType; + + private TemplateDefinitionBuilder() { + + } + + public TemplateDefinitionBuilder assetId(String assetId) { + this.assetId = assetId; + return this; + } + + public TemplateDefinitionBuilder templateId(String templateId) { + this.templateId = templateId; + return this; + } + + public TemplateDefinitionBuilder templateType(TemplateType templateType) { + this.templateType = templateType; + return this; + } + + public TemplateDefinition build() { + if (assetId == null) { + throw new IllegalStateException("assetId not defined"); + } + if (templateId == null) { + throw new IllegalStateException("templateId not defined"); + } + if (templateType == null) { + throw new IllegalStateException("templateType not defined"); + } + TemplateDefinition def = new TemplateDefinition(); + def.id = templateId; + def.type = templateType; + def.assetId = assetId; + + return def; + } + + } + + public static class TemplateVariable { + public static final String PROPERTY_NAME = "name"; + public static final String PROPERTY_OPTIONAL = "optional"; + public static final String PROPERTY_VALIDATION = "validation"; + + private String name; + private boolean optional; + private TemplateVariableValidation validation; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean isOptional() { + return optional; + } + + public void setOptional(boolean optional) { + this.optional = optional; + } + + public TemplateVariableValidation getValidation() { + return validation; + } + + public void setValidation(TemplateVariableValidation validation) { + this.validation = validation; + } + + @Override + public int hashCode() { + return Objects.hash(name, optional, validation); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TemplateVariable other = (TemplateVariable) obj; + return Objects.equals(name, other.name) && optional == other.optional && Objects.equals(validation, other.validation); + } + + } + + public static class TemplateVariableValidation { + + public static final String PROPERTY_MIN_LENGTH = "minLength"; + public static final String PROPERTY_MAX_LENGTH = "maxLength"; + public static final String PROPERTY_REGULAR_EXPRESSION = "regularExpression"; + + private Integer minLength; + private Integer maxLength; + private String regularExpression; + + public Integer getMinLength() { + return minLength; + } + + public void setMinLength(Integer minLength) { + this.minLength = minLength; + } + + public Integer getMaxLength() { + return maxLength; + } + + public void setMaxLength(Integer maxLength) { + this.maxLength = maxLength; + } + + public String getRegularExpression() { + return regularExpression; + } + + public void setRegularExpression(String regularExpression) { + this.regularExpression = regularExpression; + } + + @Override + public int hashCode() { + return Objects.hash(maxLength, minLength, regularExpression); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TemplateVariableValidation other = (TemplateVariableValidation) obj; + return Objects.equals(maxLength, other.maxLength) && Objects.equals(minLength, other.minLength) + && Objects.equals(regularExpression, other.regularExpression); + } + } + +} diff --git a/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateIdenifierConstants.java b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateIdenifierConstants.java new file mode 100644 index 0000000000..56e15f48ca --- /dev/null +++ b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateIdenifierConstants.java @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.model.template; + +import com.mercedesbenz.sechub.commons.core.MustBeKeptStable; + +/** + * Template constants - used as identifier inside database, json files but also + * for path handling inside PDS solutions. So NEVER CHANGE the content of this + * identifiers!!!! + * + * @author Albert Tregnaghi + * + */ +@MustBeKeptStable +public class TemplateIdenifierConstants { + + public static final String ID_WEBSCAN_LOGIN = "webscan-login"; + +} diff --git a/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateType.java b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateType.java new file mode 100644 index 0000000000..c2460d27c6 --- /dev/null +++ b/sechub-commons-model/src/main/java/com/mercedesbenz/sechub/commons/model/template/TemplateType.java @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.model.template; + +import static com.mercedesbenz.sechub.commons.model.template.TemplateIdenifierConstants.*; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.mercedesbenz.sechub.commons.core.MustBeKeptStable; + +/** + * Defines the template type + * + * @author Albert Tregnaghi + * + */ +@MustBeKeptStable +public enum TemplateType { + + @JsonAlias({ ID_WEBSCAN_LOGIN }) + WEBSCAN_LOGIN(ID_WEBSCAN_LOGIN); + + private String id; + + TemplateType(String id) { + this.id = id; + } + + public String getId() { + return id; + } +} diff --git a/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/template/TemplateDataResolverTest.java b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/template/TemplateDataResolverTest.java new file mode 100644 index 0000000000..cbf6cf798d --- /dev/null +++ b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/template/TemplateDataResolverTest.java @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.model.template; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.NullSource; + +import com.mercedesbenz.sechub.commons.model.JSONConverter; +import com.mercedesbenz.sechub.commons.model.SecHubConfigurationModel; + +class TemplateDataResolverTest { + + TemplateDataResolver resolverToTest; + + @BeforeEach + void beforeEach() { + resolverToTest = new TemplateDataResolver(); + } + + @Test + void webscan_login_template_data_can_be_resolved_when_defined_in_model() { + + /* prepare */ + String json = """ + { + "webScan" : { + "login" : { + + "templateData" : { + "variables" : { + "username" : "the-user", + "password" : "the-password" + } + } + } + } + } + """; + SecHubConfigurationModel model = JSONConverter.get().fromJSON(SecHubConfigurationModel.class, json); + + /* execute */ + TemplateData result = resolverToTest.resolveTemplateData(TemplateType.WEBSCAN_LOGIN, model); + + /* test */ + assertThat(result).isNotNull(); + assertThat(result.getVariables()).containsEntry("username", "the-user").containsEntry("password", "the-password"); + } + + @Test + void webscan_login_template_data_cannot_be_resolved_when_not_defined_in_model() { + + /* prepare */ + String json = """ + { + "webScan" : { + + } + } + """; + SecHubConfigurationModel model = JSONConverter.get().fromJSON(SecHubConfigurationModel.class, json); + + /* execute */ + TemplateData result = resolverToTest.resolveTemplateData(TemplateType.WEBSCAN_LOGIN, model); + + /* test */ + assertThat(result).isNull(); + } + + @ParameterizedTest + @NullSource + @EnumSource(TemplateType.class) + void template_data_is_always_null_when_model_is_empty(TemplateType templateType) { + + /* prepare */ + String json = """ + { + } + """; + SecHubConfigurationModel model = JSONConverter.get().fromJSON(SecHubConfigurationModel.class, json); + + /* execute */ + TemplateData result = resolverToTest.resolveTemplateData(templateType, model); + + /* test */ + assertThat(result).isNull(); + } + + @ParameterizedTest + @NullSource + @EnumSource(TemplateType.class) + void template_data_is_always_null_when_model_is_null(TemplateType templateType) { + + /* prepare */ + SecHubConfigurationModel model = null; + + /* execute */ + TemplateData result = resolverToTest.resolveTemplateData(templateType, model); + + /* test */ + assertThat(result).isNull(); + } + +} diff --git a/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/template/TemplateDefinitionTest.java b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/template/TemplateDefinitionTest.java new file mode 100644 index 0000000000..073460e9e4 --- /dev/null +++ b/sechub-commons-model/src/test/java/com/mercedesbenz/sechub/commons/model/template/TemplateDefinitionTest.java @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.model.template; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition.TemplateVariable; +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition.TemplateVariableValidation; +import com.mercedesbenz.sechub.test.TestFileReader; + +class TemplateDefinitionTest { + + @Test + public void json_to_from_works() throws Exception { + + /* prepare */ + + TemplateDefinition definition = TemplateDefinition.builder().templateId("identifier").templateType(TemplateType.WEBSCAN_LOGIN).assetId("asset1") + .build(); + + TemplateVariable variable1 = new TemplateVariable(); + variable1.setName("variable1"); + variable1.setOptional(true); + + TemplateVariableValidation validation = new TemplateVariableValidation(); + validation.setMinLength(1); + validation.setMaxLength(100); + validation.setRegularExpression("[a-z].*"); + variable1.setValidation(validation); + definition.getVariables().add(variable1); + + /* execute */ + String json = definition.toFormattedJSON(); + + /* test */ + TemplateDefinition deserialized = TemplateDefinition.from(json); + assertThat(deserialized.getId()).isEqualTo("identifier"); + assertThat(deserialized.getType()).isEqualTo(TemplateType.WEBSCAN_LOGIN); + assertThat(deserialized.getAssetId()).isEqualTo("asset1"); + assertThatList(deserialized.getVariables()).hasSize(1); + + TemplateVariable var1 = deserialized.getVariables().iterator().next(); + assertThat(var1).describedAs("variable1").hasNoNullFieldsOrProperties(); + assertThat(var1.getName()).isEqualTo("variable1"); + assertThat(var1.isOptional()).isTrue(); + assertThat(var1.getValidation()).isNotNull(); + + TemplateVariableValidation var1Validation = var1.getValidation(); + assertThat(var1Validation).isNotNull(); + assertThat(var1Validation.getMinLength()).isEqualTo(1); + assertThat(var1Validation.getMaxLength()).isEqualTo(100); + assertThat(var1Validation.getRegularExpression()).isEqualTo("[a-z].*"); + } + + @Test + public void example1_with_type_defined_by_id_and_not_enum_name_can_be_loaded() throws Exception { + /* prepare */ + String json = TestFileReader.readTextFromFile("./src/test/resources/template/template-definition-example1.json"); + + /* execute */ + TemplateDefinition deserialized = TemplateDefinition.from(json); + + /* test */ + assertThat(deserialized.getId()).isEqualTo("identifier"); + assertThat(deserialized.getType()).isEqualTo(TemplateType.WEBSCAN_LOGIN); + assertThat(deserialized.getAssetId()).isEqualTo("asset0815"); + assertThatList(deserialized.getVariables()).hasSize(1); + + TemplateVariable var1 = deserialized.getVariables().iterator().next(); + assertThat(var1).describedAs("variable1").hasNoNullFieldsOrProperties(); + assertThat(var1.getName()).isEqualTo("variable1"); + assertThat(var1.isOptional()).isTrue(); + assertThat(var1.getValidation()).isNotNull(); + + TemplateVariableValidation var1Validation = var1.getValidation(); + assertThat(var1Validation).isNotNull(); + assertThat(var1Validation.getMinLength()).isEqualTo(1); + assertThat(var1Validation.getMaxLength()).isEqualTo(100); + assertThat(var1Validation.getRegularExpression()).isEqualTo("[a-z].*"); + + } + +} diff --git a/sechub-commons-model/src/test/resources/template/template-definition-example1.json b/sechub-commons-model/src/test/resources/template/template-definition-example1.json new file mode 100644 index 0000000000..d07f391a88 --- /dev/null +++ b/sechub-commons-model/src/test/resources/template/template-definition-example1.json @@ -0,0 +1,14 @@ +{ + "type" : "webscan-login", + "assetId" : "asset0815", + "variables" : [ { + "name" : "variable1", + "optional" : true, + "validation" : { + "minLength" : 1, + "maxLength" : 100, + "regularExpression" : "[a-z].*" + } + } ], + "id" : "identifier" +} diff --git a/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/AbstractPDSKey.java b/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/AbstractPDSKey.java index 8fbaa33bd7..417e1735a8 100644 --- a/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/AbstractPDSKey.java +++ b/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/AbstractPDSKey.java @@ -61,7 +61,7 @@ public T markMandatory() { return (T) this; } - /* + /** * Mark this key as generated, means it will be automatically created and sent * on PDS calls */ diff --git a/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/PDSConfigDataKeyProvider.java b/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/PDSConfigDataKeyProvider.java index acfbc84ac7..77d7e9fc3d 100644 --- a/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/PDSConfigDataKeyProvider.java +++ b/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/PDSConfigDataKeyProvider.java @@ -188,7 +188,13 @@ public enum PDSConfigDataKeyProvider implements PDSKeyProvider */ PDS_MOCKING_DISABLED(new ExecutionPDSKey(PDSDefaultParameterKeyConstants.PARAM_KEY_PDS_MOCKING_DISABLED, "When 'true' any PDS adapter call will use real PDS adapter and not a mocked variant.").markForTestingOnly().markSendToPDS() - .markDefaultRecommended().withDefault(true)); + .markDefaultRecommended().withDefault(true)), + + PDS_CONFIG_TEMPLATE_META_DATA_LIST(new ExecutionPDSKey(PDSDefaultParameterKeyConstants.PARAM_KEY_PDS_CONFIG_TEMPLATE_META_DATA_LIST, """ + Contains a list of template meta data entries (json). + """).markGenerated()), + + ; private ExecutionPDSKey key; diff --git a/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/PDSDefaultParameterKeyConstants.java b/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/PDSDefaultParameterKeyConstants.java index 310c9382d6..62981afb4e 100644 --- a/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/PDSDefaultParameterKeyConstants.java +++ b/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/PDSDefaultParameterKeyConstants.java @@ -6,7 +6,7 @@ /** * All default parameter keys supported by PDS. A PDS can support optional * parameters (via its configuration) but these ones are always supported and be - * available at runtime inside PDS scripts.
+ * available at runtime inside PDS.
*
* * Wrappers can use these constants as spring boot values. @@ -78,6 +78,8 @@ public class PDSDefaultParameterKeyConstants { public static final String PARAM_KEY_PDS_CONFIG_JOBSTORAGE_READ_RESILIENCE_RETRY_WAIT_SECONDS = "pds.config.jobstorage.read.resilience.retry.wait.seconds"; + public static final String PARAM_KEY_PDS_CONFIG_TEMPLATE_META_DATA_LIST = "pds.config.template.metadata.list"; + /* ---------------------- */ /* Integration tests only */ /* ---------------------- */ diff --git a/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/PDSDefaultRuntimeKeyConstants.java b/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/PDSDefaultRuntimeKeyConstants.java index f45b60e0e1..44fcd89635 100644 --- a/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/PDSDefaultRuntimeKeyConstants.java +++ b/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/PDSDefaultRuntimeKeyConstants.java @@ -30,6 +30,8 @@ public class PDSDefaultRuntimeKeyConstants { public static final String RT_KEY_PDS_JOB_BINARIES_TAR_FILE = "pds.job.binaries.tar.file"; + public static final String RT_KEY_PDS_JOB_EXTRACTED_ASSETS_FOLDER = "pds.job.extracted.assets.folder"; + public static final String RT_KEY_PDS_JOB_EXTRACTED_SOURCES_FOLDER = "pds.job.extracted.sources.folder"; public static final String RT_KEY_PDS_JOB_HAS_EXTRACTED_BINARIES = "pds.job.has.extracted.binaries"; diff --git a/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/PDSLauncherScriptEnvironmentConstants.java b/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/PDSLauncherScriptEnvironmentConstants.java index cb8b2aa650..cf2232f0ea 100644 --- a/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/PDSLauncherScriptEnvironmentConstants.java +++ b/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/PDSLauncherScriptEnvironmentConstants.java @@ -27,6 +27,7 @@ public class PDSLauncherScriptEnvironmentConstants { @Deprecated public static final String PDS_JOB_SOURCECODE_UNZIPPED_FOLDER = "PDS_JOB_SOURCECODE_UNZIPPED_FOLDER"; + public static final String PDS_JOB_EXTRACTED_ASSETS_FOLDER = "PDS_JOB_EXTRACTED_ASSETS_FOLDER"; public static final String PDS_JOB_EXTRACTED_SOURCES_FOLDER = "PDS_JOB_EXTRACTED_SOURCES_FOLDER"; public static final String PDS_JOB_HAS_EXTRACTED_SOURCES = "PDS_JOB_HAS_EXTRACTED_SOURCES"; public static final String PDS_JOB_SOURCECODE_ZIP_FILE = "PDS_JOB_SOURCECODE_ZIP_FILE"; diff --git a/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/data/PDSTemplateMetaData.java b/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/data/PDSTemplateMetaData.java new file mode 100644 index 0000000000..8d1712ff90 --- /dev/null +++ b/sechub-commons-pds/src/main/java/com/mercedesbenz/sechub/commons/pds/data/PDSTemplateMetaData.java @@ -0,0 +1,114 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.pds.data; + +import java.util.Objects; + +import com.mercedesbenz.sechub.commons.model.template.TemplateType; + +public class PDSTemplateMetaData { + + private String templateId; + private TemplateType templateType; + private PDSAssetData assetData; + + public PDSAssetData getAssetData() { + return assetData; + } + + public String getTemplateId() { + return templateId; + } + + public TemplateType getTemplateType() { + return templateType; + } + + public void setTemplateType(TemplateType type) { + this.templateType = type; + } + + public void setTemplateId(String templateId) { + this.templateId = templateId; + } + + public void setAssetData(PDSAssetData assetData) { + this.assetData = assetData; + } + + @Override + public int hashCode() { + return Objects.hash(assetData, templateId, templateType); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + PDSTemplateMetaData other = (PDSTemplateMetaData) obj; + return Objects.equals(assetData, other.assetData) && Objects.equals(templateId, other.templateId) && templateType == other.templateType; + } + + @Override + public String toString() { + return "PDSTemplateMetaData [" + (templateId != null ? "template=" + templateId + ", " : "") + + (templateType != null ? "type=" + templateType + ", " : "") + (assetData != null ? "assetData=" + assetData : "") + "]"; + } + + public static class PDSAssetData { + private String assetId; + private String fileName; + private String checksum; + + public String getAssetId() { + return assetId; + } + + public void setAssetId(String assetId) { + this.assetId = assetId; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public String getChecksum() { + return checksum; + } + + public void setChecksum(String checksum) { + this.checksum = checksum; + } + + @Override + public int hashCode() { + return Objects.hash(assetId, checksum, fileName); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + PDSAssetData other = (PDSAssetData) obj; + return Objects.equals(assetId, other.assetId) && Objects.equals(checksum, other.checksum) && Objects.equals(fileName, other.fileName); + } + + @Override + public String toString() { + return "PDSAssetData [" + (assetId != null ? "assetId=" + assetId + ", " : "") + (fileName != null ? "fileName=" + fileName + ", " : "") + + (checksum != null ? "checksum=" + checksum : "") + "]"; + } + + } +} diff --git a/sechub-commons-pds/src/test/java/com/mercedesbenz/sechub/commons/pds/data/PDSTemplateMetaDataTest.java b/sechub-commons-pds/src/test/java/com/mercedesbenz/sechub/commons/pds/data/PDSTemplateMetaDataTest.java new file mode 100644 index 0000000000..c752a997f7 --- /dev/null +++ b/sechub-commons-pds/src/test/java/com/mercedesbenz/sechub/commons/pds/data/PDSTemplateMetaDataTest.java @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.commons.pds.data; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import com.mercedesbenz.sechub.commons.model.JSONConverter; +import com.mercedesbenz.sechub.commons.pds.data.PDSTemplateMetaData.PDSAssetData; +import com.mercedesbenz.sechub.test.TestFileReader; + +class PDSTemplateMetaDataTest { + + @ParameterizedTest + @ValueSource(strings = { "pds-param-template-metadata-example1.json", "pds-param-template-metadata-syntax.json" }) + void examples_in_doc_are_valid(String fileName) { + /* prepare */ + String json = TestFileReader.readTextFromFile("./../sechub-doc/src/docs/asciidoc/documents/shared/snippet/" + fileName); + + /* execute */ + List result = JSONConverter.get().fromJSONtoListOf(PDSTemplateMetaData.class, json); + + /* test */ + assertEquals(1, result.size()); + PDSTemplateMetaData entry = result.iterator().next(); + assertNotNull(entry.getTemplateId()); + PDSAssetData assetData = entry.getAssetData(); + assertNotNull(assetData); + assertNotNull(assetData.getAssetId()); + assertNotNull(assetData.getChecksum()); + assertNotNull(assetData.getFileName()); + } + +} diff --git a/sechub-developertools/scripts/sdc.sh b/sechub-developertools/scripts/sdc.sh index fdbfe820a2..003ca50a2d 100755 --- a/sechub-developertools/scripts/sdc.sh +++ b/sechub-developertools/scripts/sdc.sh @@ -314,15 +314,7 @@ fi if [[ "$FORMAT_CODE_ALL" = "YES" ]]; then startJob "Format all sourcecode" - openApiFilePath="$SECHUB_ROOT_DIR/sechub-doc/build/api-spec/openapi3.json" - if [ -f "$openApiFilePath" ]; then - echo ">>> Open API file exists" - else - echo ">>> Open API file DOES NOT exist - must be generated." - # Problem detected: open api file must be generated to avoid problems with gradle configuration lifecycle for open api generator! - ./gradlew generateOpenapi - fi - ./gradlew spotlessApply -Dsechub.build.stage=all + ./gradlew spotlessApply fi diff --git a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/DeveloperAdministration.java b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/DeveloperAdministration.java index 7e0304fcc5..5afb595157 100644 --- a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/DeveloperAdministration.java +++ b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/DeveloperAdministration.java @@ -22,9 +22,11 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; import com.mercedesbenz.sechub.commons.model.JSONConverter; +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition; import com.mercedesbenz.sechub.commons.pds.PDSDefaultParameterKeyConstants; import com.mercedesbenz.sechub.developertools.admin.ui.ConfigurationSetup; import com.mercedesbenz.sechub.developertools.admin.ui.UIContext; +import com.mercedesbenz.sechub.domain.scan.asset.AssetDetailData; import com.mercedesbenz.sechub.domain.scan.product.pds.PDSProductExecutorKeyConstants; import com.mercedesbenz.sechub.domain.scan.product.pds.SecHubProductExecutionPDSKeyProvider; import com.mercedesbenz.sechub.integrationtest.api.AsPDSUser; @@ -811,4 +813,49 @@ public SecHubEncryptionStatus fetchEncryptionStatus() { return asTestUser().fetchEncryptionStatus(); } + public TemplateDefinition fetchTemplateOrNull(String templateId) { + return asTestUser().fetchTemplateDefinitionOrNull(templateId); + + } + + public void createOrUpdateTemplate(String templateId, TemplateDefinition templateDefinition) { + asTestUser().createOrUpdateTemplate(templateId, templateDefinition); + } + + public void assignTemplateToProject(String templateId, String projectId) { + asTestUser().assignTemplateToProject(templateId, new FixedTestProject(projectId)); + } + + public void unassignTemplateFromProject(String templateId, String projectId) { + asTestUser().unassignTemplateFromProject(templateId, new FixedTestProject(projectId)); + } + + public List fetchAllTemplateIdentifiers() { + return asTestUser().fetchTemplateList(); + } + + public List fetchAllAssetIdentifiers() { + return asTestUser().fetchAllAssetIds(); + } + + public void uploadAssetFile(String assetId, File file) { + asTestUser().uploadAssetFile(assetId, file); + } + + public AssetDetailData fetchAssetDetails(String assetId) { + return asTestUser().fetchAssetDetails(assetId); + } + + public void deleteAsset(String assetId) { + asTestUser().deleteAsset(assetId); + } + + public void deleteAssetFile(String assetId, String fileName) { + asTestUser().deleteAssetFile(assetId, fileName); + } + + public File downloadAssetFile(String assetId, String fileName) { + return asTestUser().downloadAssetFile(assetId, fileName); + } + } diff --git a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/CommandUI.java b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/CommandUI.java index 11a85dc139..cdbefba643 100644 --- a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/CommandUI.java +++ b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/CommandUI.java @@ -20,6 +20,7 @@ import com.mercedesbenz.sechub.developertools.admin.ui.action.ActionSupport; import com.mercedesbenz.sechub.developertools.admin.ui.action.adapter.ShowProductExecutorTemplatesDialogAction; +import com.mercedesbenz.sechub.developertools.admin.ui.action.asset.ManageAssetsAction; import com.mercedesbenz.sechub.developertools.admin.ui.action.client.TriggerSecHubClientSynchronousScanAction; import com.mercedesbenz.sechub.developertools.admin.ui.action.config.ConfigureAutoCleanupAction; import com.mercedesbenz.sechub.developertools.admin.ui.action.config.ConfigurePDSAutoCleanupAction; @@ -96,6 +97,10 @@ import com.mercedesbenz.sechub.developertools.admin.ui.action.scheduler.RefreshSchedulerStatusAction; import com.mercedesbenz.sechub.developertools.admin.ui.action.status.CheckStatusAction; import com.mercedesbenz.sechub.developertools.admin.ui.action.status.ListStatusEntriesAction; +import com.mercedesbenz.sechub.developertools.admin.ui.action.template.AssignTemplateToProjectAction; +import com.mercedesbenz.sechub.developertools.admin.ui.action.template.CreateOrUpdateTemplateAction; +import com.mercedesbenz.sechub.developertools.admin.ui.action.template.FetchAllTemplateIdentifiersAction; +import com.mercedesbenz.sechub.developertools.admin.ui.action.template.UnassignTemplateFromProjectAction; import com.mercedesbenz.sechub.developertools.admin.ui.action.user.AcceptUserSignupAction; import com.mercedesbenz.sechub.developertools.admin.ui.action.user.AnonymousRequestNewAPITokenUserAction; import com.mercedesbenz.sechub.developertools.admin.ui.action.user.AnonymousSigninNewUserAction; @@ -250,6 +255,17 @@ public void createConfigMenu() { add(mappingsMenu, new UpdateGlobalMappingAction(context)); menu.add(new ConfigureAutoCleanupAction(context)); + + JMenu templatesMenu = new JMenu("Templates"); + menu.add(templatesMenu); + add(templatesMenu, new CreateOrUpdateTemplateAction(context)); + add(templatesMenu, new FetchAllTemplateIdentifiersAction(context)); + + add(templatesMenu, new AssignTemplateToProjectAction(context)); + add(templatesMenu, new UnassignTemplateFromProjectAction(context)); + + menu.add(new ManageAssetsAction(context)); + } public void createEncryptionMenu() { diff --git a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/ManageAssetsDialogUI.java b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/ManageAssetsDialogUI.java new file mode 100644 index 0000000000..24abb248c4 --- /dev/null +++ b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/ManageAssetsDialogUI.java @@ -0,0 +1,280 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.developertools.admin.ui; + +import java.awt.BorderLayout; +import java.awt.event.ActionEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.File; +import java.util.List; +import java.util.Optional; + +import javax.swing.JFrame; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JScrollPane; +import javax.swing.JSplitPane; +import javax.swing.JTextArea; +import javax.swing.JToolBar; +import javax.swing.JTree; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; + +import com.mercedesbenz.sechub.developertools.admin.ui.action.AbstractUIAction; +import com.mercedesbenz.sechub.developertools.admin.ui.cache.InputCacheIdentifier; +import com.mercedesbenz.sechub.domain.scan.asset.AssetDetailData; +import com.mercedesbenz.sechub.domain.scan.asset.AssetFileData; + +public class ManageAssetsDialogUI { + + private JFrame frame; + private UIContext context; + private DefaultMutableTreeNode root; + private JTree tree; + private JTextArea textArea; + + public ManageAssetsDialogUI(UIContext context) { + this.context = context; + + frame = new JFrame(); + frame.setLayout(new BorderLayout()); + + createMenuBar(context); + + createToolBar(context); + + frame.setTitle("Manage assets"); + + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + + frame.setSize(800, 600); + frame.setLocationRelativeTo(null); + + root = new DefaultMutableTreeNode(new AssetRootElement()); + DefaultTreeModel model = new DefaultTreeModel(root); + tree = new JTree(model); + + textArea = new JTextArea(); + JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, new JScrollPane(tree), new JScrollPane(textArea)); + frame.add(splitPane, BorderLayout.CENTER); + + tree.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); + if (node == null) { + return; + } + + Object userObject = node.getUserObject(); + if (userObject instanceof AssetRootElement) { + refreshModel(); + return; + } + if (userObject instanceof AssetElement) { + node.removeAllChildren(); + + AssetElement element = (AssetElement) userObject; + String assetId = element.assetId; + AssetDetailData detailData = context.getAdministration().fetchAssetDetails(assetId); + textArea.setText(detailData.toFormattedJSON()); + int added = 0; + for (AssetFileData info : detailData.getFiles()) { + node.add(new DefaultMutableTreeNode(new AssetFileElement(assetId, info.getFileName(), info.getChecksum()))); + added++; + } + element.info = added + " files"; + } + tree.repaint(); + } + } + }); + } + + private void createToolBar(UIContext context) { + JToolBar toolbar = new JToolBar(); + toolbar.add(new RefresAction(context)); + toolbar.addSeparator(); + toolbar.add(new UploadAssetFileAction(context)); + toolbar.add(new DownloadAssetFileAction(context)); + toolbar.addSeparator(); + toolbar.add(new DeleteAction(context)); + + frame.add(toolbar, BorderLayout.NORTH); + } + + private void createMenuBar(UIContext context) { + JMenuBar menuBar = new JMenuBar(); + JMenu menu1 = new JMenu("Actions"); + menuBar.add(menu1); + menu1.add(new RefresAction(context)); + menu1.addSeparator(); + menu1.add(new UploadAssetFileAction(context)); + menu1.add(new DownloadAssetFileAction(context)); + menu1.addSeparator(); + menu1.add(new DeleteAction(context)); + + frame.setJMenuBar(menuBar); + } + + public static void main(String[] args) { + new ManageAssetsDialogUI(null).show(); + } + + public void show() { + frame.setVisible(true); + } + + private void refreshModel() { + root.removeAllChildren(); + List assetIdentifiers = context.getAdministration().fetchAllAssetIdentifiers(); + textArea.setText("Loaded asset identifiers:\n" + assetIdentifiers); + AssetRootElement rootElement = (AssetRootElement) root.getUserObject(); + rootElement.info = "Loaded: " + assetIdentifiers.size(); + + for (String assetId : assetIdentifiers) { + root.add(new DefaultMutableTreeNode(new AssetElement(assetId))); + } + tree.setModel(new DefaultTreeModel(root)); + } + + private class AssetRootElement { + private String info = "Double click to load"; + + @Override + public String toString() { + return "assets (" + info + ")"; + } + } + + private class AssetElement { + private String assetId; + private String info = "Double click to load"; + + private AssetElement(String assetId) { + this.assetId = assetId; + } + + @Override + public String toString() { + return assetId + "(" + info + ")"; + } + + } + + private class AssetFileElement { + + private String checksum; + private String fileName; + private String assetId; + + public AssetFileElement(String assetId, String fileName, String checksum) { + this.assetId = assetId; + this.fileName = fileName; + this.checksum = checksum; + } + + @Override + public String toString() { + return fileName + " (" + checksum + ")"; + } + + } + + private class RefresAction extends AbstractUIAction { + + public RefresAction(UIContext context) { + super("Refresh", context); + } + + private static final long serialVersionUID = 7392018849800602872L; + + @Override + protected void execute(ActionEvent e) throws Exception { + refreshModel(); + } + + } + + private class UploadAssetFileAction extends AbstractUIAction { + + public UploadAssetFileAction(UIContext context) { + super("Upload", context); + } + + private static final long serialVersionUID = 7392018849800602872L; + + @Override + protected void execute(ActionEvent e) throws Exception { + + Optional assetIdOpt = getUserInput("Select asset id", InputCacheIdentifier.ASSET_ID); + if (assetIdOpt.isEmpty()) { + return; + } + File file = getContext().getDialogUI().selectFile(null); + if (file == null) { + return; + } + String assetId = assetIdOpt.get(); + getContext().getAdministration().uploadAssetFile(assetId, file); + } + + } + + private class DownloadAssetFileAction extends AbstractUIAction { + + public DownloadAssetFileAction(UIContext context) { + super("Download", context); + } + + private static final long serialVersionUID = 7392018849800602872L; + + @Override + protected void execute(ActionEvent e) throws Exception { + DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); + Object userObject = node.getUserObject(); + + if (userObject instanceof AssetFileElement) { + AssetFileElement assetElement = (AssetFileElement) userObject; + File file = getContext().getAdministration().downloadAssetFile(assetElement.assetId, assetElement.fileName); + textArea.setText("Downloaded to:\n" + file.getAbsolutePath()); + } + } + + } + + private class DeleteAction extends AbstractUIAction { + + public DeleteAction(UIContext context) { + super("Delete", context); + } + + private static final long serialVersionUID = 7392018849800602872L; + + @Override + protected void execute(ActionEvent e) throws Exception { + DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent(); + Object userObject = node.getUserObject(); + if (userObject instanceof AssetElement) { + AssetElement assetElement = (AssetElement) userObject; + String assetId = assetElement.assetId; + if (!getContext().getDialogUI().confirm("Do you really want to delete complete asset:" + assetId)) { + return; + } + getContext().getAdministration().deleteAsset(assetId); + + } else if (userObject instanceof AssetFileElement) { + AssetFileElement assetElement = (AssetFileElement) userObject; + String assetId = assetElement.assetId; + String fileName = assetElement.fileName; + + if (!getContext().getDialogUI().confirm("Do you really want to delete file:" + fileName + " from asset:" + assetId)) { + return; + } + getContext().getAdministration().deleteAssetFile(assetId, fileName); + } + } + + } + +} diff --git a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/asset/ManageAssetsAction.java b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/asset/ManageAssetsAction.java new file mode 100644 index 0000000000..0368e97e66 --- /dev/null +++ b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/asset/ManageAssetsAction.java @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.developertools.admin.ui.action.asset; + +import java.awt.event.ActionEvent; + +import com.mercedesbenz.sechub.developertools.admin.ui.ManageAssetsDialogUI; +import com.mercedesbenz.sechub.developertools.admin.ui.UIContext; +import com.mercedesbenz.sechub.developertools.admin.ui.action.AbstractUIAction; + +public class ManageAssetsAction extends AbstractUIAction { + private static final long serialVersionUID = 1L; + + public ManageAssetsAction(UIContext context) { + super("Manage assets", context); + } + + @Override + public void execute(ActionEvent e) { + + ManageAssetsDialogUI ui = new ManageAssetsDialogUI(getContext()); + ui.show(); + + } + +} \ No newline at end of file diff --git a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/template/AssignTemplateToProjectAction.java b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/template/AssignTemplateToProjectAction.java new file mode 100644 index 0000000000..10b63857a1 --- /dev/null +++ b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/template/AssignTemplateToProjectAction.java @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.developertools.admin.ui.action.template; + +import java.awt.event.ActionEvent; +import java.util.Optional; + +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition; +import com.mercedesbenz.sechub.developertools.admin.ui.UIContext; +import com.mercedesbenz.sechub.developertools.admin.ui.action.AbstractUIAction; +import com.mercedesbenz.sechub.developertools.admin.ui.cache.InputCacheIdentifier; + +public class AssignTemplateToProjectAction extends AbstractUIAction { + private static final long serialVersionUID = 1L; + + public AssignTemplateToProjectAction(UIContext context) { + super("Assign template to project", context); + } + + @Override + public void execute(ActionEvent e) { + Optional templateIdOpt = getUserInput("Please enter templateId", InputCacheIdentifier.TEMPLATE_ID); + if (!templateIdOpt.isPresent()) { + return; + } + String templateId = templateIdOpt.get(); + TemplateDefinition foundTemplate = getContext().getAdministration().fetchTemplateOrNull(templateId); + if (foundTemplate == null) { + error("The template " + templateId + " does not exist!"); + return; + } + Optional projectIdOpt = getUserInput("Please enter projectId", InputCacheIdentifier.PROJECT_ID); + if (!projectIdOpt.isPresent()) { + return; + } + String projectId = projectIdOpt.get(); + String projectInfo = getContext().getAdministration().fetchProjectInfo(projectId); + + if (projectInfo == null) { + error("The project " + projectId + " does not exist!"); + return; + } + + getContext().getAdministration().assignTemplateToProject(templateId, projectId); + + } + +} \ No newline at end of file diff --git a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/template/CreateOrUpdateTemplateAction.java b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/template/CreateOrUpdateTemplateAction.java new file mode 100644 index 0000000000..3aa8002b15 --- /dev/null +++ b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/template/CreateOrUpdateTemplateAction.java @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.developertools.admin.ui.action.template; + +import java.awt.event.ActionEvent; +import java.util.Optional; + +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition; +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition.TemplateVariable; +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition.TemplateVariableValidation; +import com.mercedesbenz.sechub.commons.model.template.TemplateType; +import com.mercedesbenz.sechub.developertools.admin.ui.UIContext; +import com.mercedesbenz.sechub.developertools.admin.ui.action.AbstractUIAction; +import com.mercedesbenz.sechub.developertools.admin.ui.cache.InputCacheIdentifier; + +public class CreateOrUpdateTemplateAction extends AbstractUIAction { + private static final long serialVersionUID = 1L; + + public CreateOrUpdateTemplateAction(UIContext context) { + super("Create or update template", context); + } + + @Override + public void execute(ActionEvent e) { + Optional templateIdOpt = getUserInput("Please enter templateId", InputCacheIdentifier.TEMPLATE_ID); + if (!templateIdOpt.isPresent()) { + return; + } + + String dialogTitle = null; + String templateId = templateIdOpt.get(); + TemplateDefinition templateDefinition = getContext().getAdministration().fetchTemplateOrNull(templateId); + if (templateDefinition == null) { + /* we create an example here */ + + String title = "Create new template:" + templateId; + String message = "Please enter template type"; + Optional templateTypeOpt = getUserInputFromCombobox(title, TemplateType.WEBSCAN_LOGIN, message, TemplateType.values()); + if (!templateTypeOpt.isPresent()) { + return; + } + templateDefinition = TemplateDefinition.builder().templateId(templateId).templateType(templateTypeOpt.get()).assetId("example-asset-id").build(); + TemplateVariable exampleVariable = new TemplateVariable(); + exampleVariable.setName("example-variable"); + exampleVariable.setOptional(true); + TemplateVariableValidation validation = new TemplateVariableValidation(); + validation.setMinLength(2); + validation.setMaxLength(100); + validation.setRegularExpression("[0-9a-z].*"); + + exampleVariable.setValidation(validation); + templateDefinition.getVariables().add(exampleVariable); + + dialogTitle = "New Template:" + templateId + " (by example)"; + + } else { + dialogTitle = "Change existing template:" + templateId; + } + + Optional templateDefInputOpt = getUserInputFromTextArea(dialogTitle, templateDefinition.toFormattedJSON()); + if (templateDefInputOpt.isEmpty()) { + return; + } + + TemplateDefinition updatedTemplateDefinition = TemplateDefinition.from(templateDefInputOpt.get()); + + getContext().getAdministration().createOrUpdateTemplate(templateId, updatedTemplateDefinition); + } + +} \ No newline at end of file diff --git a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/template/FetchAllTemplateIdentifiersAction.java b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/template/FetchAllTemplateIdentifiersAction.java new file mode 100644 index 0000000000..763a9b163b --- /dev/null +++ b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/template/FetchAllTemplateIdentifiersAction.java @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.developertools.admin.ui.action.template; + +import java.awt.event.ActionEvent; +import java.util.List; + +import com.mercedesbenz.sechub.developertools.admin.ui.UIContext; +import com.mercedesbenz.sechub.developertools.admin.ui.action.AbstractUIAction; + +public class FetchAllTemplateIdentifiersAction extends AbstractUIAction { + private static final long serialVersionUID = 1L; + + public FetchAllTemplateIdentifiersAction(UIContext context) { + super("Fetch all template identifiers", context); + } + + @Override + public void execute(ActionEvent e) { + List identifiers = getContext().getAdministration().fetchAllTemplateIdentifiers(); + output("Found template identiiers:\n" + identifiers.toString()); + } + +} \ No newline at end of file diff --git a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/template/UnassignTemplateFromProjectAction.java b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/template/UnassignTemplateFromProjectAction.java new file mode 100644 index 0000000000..2ec9ef90c3 --- /dev/null +++ b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/action/template/UnassignTemplateFromProjectAction.java @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.developertools.admin.ui.action.template; + +import java.awt.event.ActionEvent; +import java.util.Optional; + +import com.mercedesbenz.sechub.developertools.admin.ui.UIContext; +import com.mercedesbenz.sechub.developertools.admin.ui.action.AbstractUIAction; +import com.mercedesbenz.sechub.developertools.admin.ui.cache.InputCacheIdentifier; + +public class UnassignTemplateFromProjectAction extends AbstractUIAction { + private static final long serialVersionUID = 1L; + + public UnassignTemplateFromProjectAction(UIContext context) { + super("Unassign template from project", context); + } + + @Override + public void execute(ActionEvent e) { + Optional templateIdOpt = getUserInput("Please enter templateId", InputCacheIdentifier.TEMPLATE_ID); + if (!templateIdOpt.isPresent()) { + return; + } + String templateId = templateIdOpt.get(); + Optional projectIdOpt = getUserInput("Please enter projectId", InputCacheIdentifier.PROJECT_ID); + if (!projectIdOpt.isPresent()) { + return; + } + String projectId = projectIdOpt.get(); + + getContext().getAdministration().unassignTemplateFromProject(templateId, projectId); + + } + +} \ No newline at end of file diff --git a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/cache/InputCacheIdentifier.java b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/cache/InputCacheIdentifier.java index e6a9238208..931ff7b8d1 100644 --- a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/cache/InputCacheIdentifier.java +++ b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/ui/cache/InputCacheIdentifier.java @@ -50,4 +50,8 @@ public enum InputCacheIdentifier { PAGE_SIZE, + TEMPLATE_ID, + + ASSET_ID, + } \ No newline at end of file diff --git a/sechub-doc/src/docs/asciidoc/diagrams/diagram_templates-and-assets-big-picture.puml b/sechub-doc/src/docs/asciidoc/diagrams/diagram_templates-and-assets-big-picture.puml new file mode 100644 index 0000000000..559f69408c --- /dev/null +++ b/sechub-doc/src/docs/asciidoc/diagrams/diagram_templates-and-assets-big-picture.puml @@ -0,0 +1,39 @@ +@startuml + +skinparam linetype ortho + +actor user + +rectangle "SecHub configuration file" as config #lightgreen + +rectangle "Template data" as templateData #lightgreen + +rectangle "SecHub\nJob" as sechub + +rectangle "Access to extracted\nproduct specific template files\n(from asset)" as assetFile #darkorange + +rectangle "PDS\nJob" as pds +rectangle "PDS launcher script\nfor PDS product" as pdsLauncherScript + +rectangle "Template\ndefinition" as templateDefinition +rectangle "Project" as project +rectangle "Asset" as asset + +user->config +config ->sechub +config o--templateData + +pds ..> asset + +sechub ..> project +project ..> templateDefinition +sechub -> pds + +templateDefinition .> asset + +pds -> pdsLauncherScript + +pdsLauncherScript ..> assetFile + +@enduml + diff --git a/sechub-doc/src/docs/asciidoc/diagrams/diagram_templates-mapping-to-projects-events.puml b/sechub-doc/src/docs/asciidoc/diagrams/diagram_templates-mapping-to-projects-events.puml new file mode 100644 index 0000000000..509fbc4266 --- /dev/null +++ b/sechub-doc/src/docs/asciidoc/diagrams/diagram_templates-mapping-to-projects-events.puml @@ -0,0 +1,52 @@ +' SPDX-License-Identifier: MIT +@startuml + +'Hide empty parts: +hide empty fields +hide empty methods + +'You can find more examles at https://plantuml.com/class-diagram + + +node scan { + + class ScanProjectConfig { + String projectId + String key + String value + } +} + + +node administration{ + class Project { + String projectId + List templates + } + +} + +node eventBus as eventBus{ +} + +administration --> eventBus: REQUEST_ASSIGN_TEMPLATE_TO_PROJECT (1) + +eventBus--> scan: REQUEST_ASSIGN_TEMPLATE_TO_PROJECT (2) + +scan --> eventBus: RESULT_ASSIGN_TEMPLATE_TO_PROJECT (3) + + +note top of ScanProjectConfig +If the there exists already an entry for key "TEMPLATE_$templateType" +the config entry will be replaced. otherwise created. +end note + +note top of administration +After template mapping result is returned by scan domain, +the assigned template list will be updated. + +If a failure happend - e.g. template with given id does +not exist, the scan domain will return a failure +and this failure will be returned to caller side. +end note +@enduml diff --git a/sechub-doc/src/docs/asciidoc/diagrams/diagram_templates-mapping-to-projects.puml b/sechub-doc/src/docs/asciidoc/diagrams/diagram_templates-mapping-to-projects.puml new file mode 100644 index 0000000000..c88bb9438b --- /dev/null +++ b/sechub-doc/src/docs/asciidoc/diagrams/diagram_templates-mapping-to-projects.puml @@ -0,0 +1,61 @@ +' SPDX-License-Identifier: MIT +@startuml + +'Hide empty parts: +hide empty fields +hide empty methods + +'You can find more examles at https://plantuml.com/class-diagram + + +node scan { + + class Template { + String id + TemplateDefinition definition + } + + class TemplateDefinition { + TemplateType type + ... + + } + + class ScanProjectConfig { + String projectId + String key + String value + } +} + + +node administration{ + class Project { + String projectId + List templates + } + +} + +database "Scan\ndatabase" as DB1 { +} + +database "Administration\ndatabase" as DB2 { +} + +TemplateDefinition . Template +Template --> DB1 +ScanProjectConfig --> DB1 + +Project --> DB2 + + +note top of ScanProjectConfig +We use the existing concept of scan project +configuration to handle project related +template setup: + +The key will be always "TEMPLATE_$templateType" +and the value is the template identifier. +end note +@enduml diff --git a/sechub-doc/src/docs/asciidoc/documents/pds/pds_config.adoc b/sechub-doc/src/docs/asciidoc/documents/pds/pds_config.adoc index d4bc584562..9e798d1065 100644 --- a/sechub-doc/src/docs/asciidoc/documents/pds/pds_config.adoc +++ b/sechub-doc/src/docs/asciidoc/documents/pds/pds_config.adoc @@ -48,7 +48,7 @@ Either use system properties pds.techuser.userid pds.techuser.apitoken ---- -or env entries +or environment variables ---- PDS_TECHUSER_USERID PDS_TECHUSER_APITOKEN @@ -187,6 +187,12 @@ how the data is gathered. |PDS_JOB_SOURCECODE_ZIP_FILE | The absolute path to the uploaded "sourcecode.zip" file |PDS_JOB_EXTRACTED_SOURCES_FOLDER | When auto extracting is enabled (default) the uploaded source code is extracted to this folder |PDS_JOB_EXTRACTED_BINARIES_FOLDER | When auto extracting is enabled (default) the uploaded binaries are extracted to this folder +|PDS_JOB_EXTRACTED_ASSETS_FOLDER | The absolute path to the extracted assets. + + + + Files for template types are located in dedicated sub folders which are named by the id of the template type. + + + + + For example: WEBSCAN_LOGIN template type has id: `webscan-login`. To access the file `custom-login.groovy` a script + or a wrapper application can simply fetch the file via `$PDS_JOB_EXTRACTED_ASSETS_FOLDER/webscan-login/custom-login.groovy`. |PDS_SCAN_TARGET_URL | Target URL for current scan (e.g webscan). Will not be set in all scan types. E.g. for a code scan this environemnt variable will not be available |PDS_SCAN_CONFIGURATION | Contains the SecHub configuration as JSON _(but reduced to current scan type, so e.g. a web scan will have no code scan configuration data available)_ + + @@ -216,10 +222,15 @@ include::../gen/gen_pds_executor_config_parameters.adoc[] ==== File locations ===== Upload -`$PDS_JOB_WORKSPACE_LOCATION/upload/` +Content from uploaded user archives is extracted to: + +`PDS_JOB_EXTRACTED_SOURCES_FOLDER`, + +`PDS_JOB_EXTRACTED_BINARIES_FOLDER` + +Content from uploaded asset files is extracted to: + `PDS_JOB_EXTRACTED_ASSETS_FOLDER`, -Automatically unzipped content is available inside + -`$PDS_JOB_WORKSPACE_LOCATION/upload/unzipped` ===== Output diff --git a/sechub-doc/src/docs/asciidoc/documents/shared/concepts/concept_templates_and_assets.adoc b/sechub-doc/src/docs/asciidoc/documents/shared/concepts/concept_templates_and_assets.adoc index 2e9492cfa9..bd1361d3c7 100644 --- a/sechub-doc/src/docs/asciidoc/documents/shared/concepts/concept_templates_and_assets.adoc +++ b/sechub-doc/src/docs/asciidoc/documents/shared/concepts/concept_templates_and_assets.adoc @@ -7,70 +7,101 @@ and - depending on the product - very hard to provide a complete generic approac only. ===== Example situation -To clarify this abstract situation here an example: + +To clarify this abstract situation here an example: Think about a wide range of applications inside +a corporation which want to use a common single sign on with multi factor authentication and some +other specific parts. The login configuration in {sechub} configuration file by defining explicit steps, pages etc. +would become complex and error prone because : -Think about a wide range of applications inside a corporation which want to use a common -single sign on with multi factor authentication and some other specific parts. - -The login configuration would become complex and error prone because not every user would -understand exactly what to do. If the single-signon mechanism would change, every project/user -would need to change their {sechub} configuration files! When talking about hundreds of projects -this would be a great effort - for users and for support! +- not every user would understand exactly what to do +- if the single signon mechanism ever changes, every project/user would need to change their {sechub} configuration files! + When talking about hundreds of projects this would be a great effort - for users and for support! And let us assume the used product has hundreds of options which cannot be configured by a generic approach easily (e.g. form login definition in configuration): How could this product -be provided in a desired way then? - -And what if there are some specific parts which are not 100% same in every of these projects? +be provided in a desired way then? And what if there are some specific parts which are not 100% same +in every of these projects? This is the reason for the "templates and assets" concept which is described below. +===== Big picture +plantuml::./diagrams/diagram_templates-and-assets-big-picture.puml[format=svg, title="Big picture of templates and assets"] + ===== Templates -{sechub} can have multiple templates . Every template has a type and can contains a set of -assets and also a variable definition. It can be administrated via -REST end points: +{sechub} can have multiple templates. Every template has a type and can contains a set of +assets and also a variable definition. It can be administrated via REST end points. -``` -PUT /api/administration/template/$template-id -GET /api/administration/template/$template-id -DELETE /api/administration/template/$template-id -``` +====== Template id format +A template id -PUT will contain following body: +- has a minimum length of 3 +- has a maximum length of 40 +- can contain only `a-z` `A-Z`, `0-9` or `-` or `_` + +====== Template definition +The template definitions are hold inside {sechub} database. +A template is defined by following json syntax: +.Template definition syntax [source,json] ---- -{ - "type" : "webscan-login",//<1> - "assets" : ["asset-id-1"]//<2> - "variables" : {//<3> - "username" : "mandatory",//<4> - "password" : "mandatory", - "tip-of-the-day" : "optional"//<5> - } -} +include::../snippet/template-definition-syntax.json[] ---- -<1> The type of the template. Currently possible: `webscan-login` -<2> Array with asset identifiers assigned to the template -<3> Variable definitions as list of key and value pairs which can be mandatory or optional. +<1> The template identifier +<2> The type of the template. Must be set when a template is created. Will + be ignored on updates. Possible values are: `webscan-login` +<3> Asset identifier for the template +<4> Variable definitions as list of key and value pairs which can be mandatory or optional. Via the type {sechub} server is able to check if the configuration is valid and give response to users when job starts. +<5> Name of the variable +<6> Describes if the variable is optional. + + When `false` configuration must contain the variable inside template data. When `true` + the configuration is valid without the variable. The default is `false`. +<7> Variable content validation definition ((optional) +<8> Minimum length (optional) +<9> Maximum length (optional) +<10> Regular expression (optional). If defined, the content must match the given regular expression + to be valid. + +[CAUTION] +==== +The validation section inside the definition is only for a "first simple check" on {sechub} side, to +stop before any {pds} job is created. -===== Mapping templates to projects -``` -PUT /api/admin/project/$projectId/template/$templateid -DELETE /api/admin/project/$projectId/template/$templateid -GET /api/admin/project/$projectId/template/list -``` +*But {pds} solutions which are using templates must ensure,that the given user content +(variable) is correct and not some malicious injected data!* +==== +====== Mapping templates to projects -[IMPORTANT] +plantuml::./diagrams/diagram_templates-mapping-to-projects.puml[format=svg, title="Mapping of templates and projects"] + +As shown in the figure above, the template data is hold inside domain "scan". The reason is, that +template details are necessary inside the scan operation and no where else. + +The association between project and used templates is done in administration domain because the project +entity and its details are used here. At the `administration` domain only the template identifiers +used by this project are necessary. + +A mapping of templates to a project is dependent on the template type: Same type of a template cannot +be added twice to a project! Because the `administration` domain does not know about the template details, +a synchronous event to `scan` domain will be sent to assign the template to the project. + +The scan domain will update this information inside `ScanProjectConfig` entities and will drop +a former assigned template of same type from the config. + +After this is done as a syncron result the scan domain will return all assigned +templates, as shown in next figure: + +plantuml::./diagrams/diagram_templates-mapping-to-projects-events.puml[format=svg, title="Mapping of templates and projects (Events)"] + +[NOTE] ==== The mapping is stored with template type as part of the composite key: If two template definitions with same type are uploaded, the last one will overwrite the first one. ==== -===== SecHub configuration file +====== Templates in SecHub configuration file The users can now define in their {sechub} configuration file that they want to use the configured template for web scanning by defining a templateData section inside the login configuration. @@ -84,52 +115,29 @@ include::../configuration/sechub_config_example22_webscan_with_template.json[] is not able to define which template is used. <2> Setup template variables by a list of key and value pairs. -[IMPORTANT] -==== If the user has defined a `templateData` section in the configuration but no template of that type is assigned to the project, a dedicated error message must be returned to the user and the {sechub} job will always fail. -==== - -===== Asset storage -The assets are initially stored in {sechub} database, together with the asset file checksum. - -When a template is used inside a {sechub} configuration, the {sechub} start mechanism will -check if the file from database is already available at storage (S3/NFS) which represents -a cache for the assets. - -If available, the cached part will be used. Otherwise it will be uploaded -(see <> ) - - -[TIP] -==== -Main reason why we store always in DB and use storage (S3/NFS) as a cache only: - -1. Restore a DB backup on a fresh installation: + - Administrators can just apply the database backup and everything works again -2. Storage could be volatile/be deleted etc. (we rely to the database) -3. For having two clusters ( {sechub} and {pds} having not same database we need - a way to exchange. -==== - [IMPORTANT] ==== -For templates and assets we use the shared storage between {sechub} and {pds} . -solutions and not to provide own storage for {pds}. Diffeent storages are not supported here. +For templates and assets we must use shared storage between {sechub} and {pds}. {pds} solutions may +not to provide own storage for {pds} - different storages are not supported here. ==== -`/assets/$assetId/` +===== Assets +====== Asset id format +An asset id -An asset id has - -- maximum length of 40 +- has a minimum length of 3 +- has a maximum length of 40 - can contain only `a-z` `A-Z`, `0-9` or `-` or `_` -At the storage for each product (as named in the `pds-config.json` of the {pds} solution), there will be a ZIP file having the -product identifier as filename +====== {sechub} server uses product identifiers for asset ids +The {sechub} server will use the product identifiers from {pds} executor configurations +as file names when storing asset data inside database and storage. The product identifiers +are also defined inside the {pds} server configuration files. -For example: +Here an example for storage paths: ``` /assets/ asset-id-1/ @@ -138,39 +146,62 @@ For example: OTHER_PRODUCT_X.zip OTHER_PRODUCT_X.zip.sha256 ``` -[TIP] +[IMPORTANT] ==== -The content and the structure inside the ZIP files is absolute solution specific : -The PDS solution defines which kind of template data is necessary and how the -structure looks like! +The content and the structure inside the ZIP files is absolute solution specific: +Means the {pds} solution defines which kind of template data is necessary and how the +structure looks like. Because of this necessary template variables, file structure for +asset zip files etc. must be documented inside the {pds} solution! ==== -===== Administration REST end points for storage +====== Asset operations +*General* + +*Usecases* + +- Admin creates or updates asset +- + Over REST API Administrators will be able to -- list (ZIP) file names + - `GET /api/administration/asset/$assetId/list` -- upload asset ZIP files by REST API and create checksum file + - `PUT /api/administration/asset/$assetId/$productId` -- download asset files by REST API + - `GET /api/administration/asset/$assetId/$productId` -- delete single asset file by REST API + - `DELETE /api/administration/asset/$assetId/$productId` -- delete all asset files by REST API + - `DELETE /api/administration/asset/$assetId` +- upload asset ZIP files by REST API and create checksum file +- download asset files by REST API +- list asset details (filenames and checksums) +- delete single asset file by REST API +- delete all asset data by REST API The REST API will always create an audit log entry +*Upload* + +The upload and delete operations will always handle DB and storage (S3/NFS)! +If an asset ZIP file already exists, the operation will overwrite the file and the +old sha256 checksum in both locations. + +[TIP] +==== +Main reason why we store always in DB and use storage (S3/NFS) as a cache only: + +1. Restore a DB backup on a fresh installation: + + Administrators can just apply the database backup and everything works again +2. Storage could be volatile/be deleted etc. (we rely to the database) +3. For having multiple and also different cluster types ( {sechub} cluster and multiple {pds} clusters) + having not same database we need a common way to exchange data. + Each {pds} cluster and also the {sechub} cluster have their own + database - means cannot be used for exchange. +==== + + [IMPORTANT] ==== This works only when the storage is shared between {sechub} and {pds}. If the {pds} uses its own storage (which should NOT be done for production, but only -for PDS solution development) the assets must be uploaded directly to the PDS storage -location! +for PDS solution development) the assets would needed to be uploaded directly to the PDS storage +location with correct checksum etc. ==== -===== SecHub template and asset handling -====== Validation +===== SecHub runtime +====== Validate config uses valid template location When a {sechub} job starts and there is a template data definition inside the configuration, {sechub} will validate if the project has a template assigned for the location inside the configuration. e.g. templates with type `webscan-login` may only be defined inside @@ -179,51 +210,43 @@ web scan login configuration). If this validation fails, the complete {sechub} job will fail and stop processing. [[sechub-concept-asset-upload-lazy]] -====== Upload in storage as cache lazily -When validation did not fail, {sechub} will check if the current version of the asset is already uploaded to -storage (S3/NFS) already. +====== Validate config uses valid template location + +When validation did not fail, {sechub} will check if the current version of the +necessary product specific asset ZIP file is exsting in storage. -When not uploaded to storage, the file will be uploaded before the job is further processed. +When not uploaded to storage, the file will be uploaded before the job is +further processed. ====== Template PDS parameter calculation -If validation and lazy upload did not fail, {sechub} will calculate a -{pds} parameter : `pds.template.metadata` with JSON (see parameter syntax for details). +If the job configuration has valid template data and the template +is available in storage, {sechub} will calculate the +{pds} parameter : `pds.config.template.metadata.list` with JSON: -.PDS parameter syntax for template meta data +[[sechub-concept-template-metadata-example]] +.PDS parameter syntax for template meta data [source,json] ---- -{ - "template-metadata" : [ //<1> - { - "template" : "single-singon", //<2> - "type": "webscan-login",//<3> - "assets" : [ //<4> - { - "id" : "asset-id-1", //<5> - "sha256" : "434c6c6ec1b0ed9844149069d7d45ac18e72505b" //<6> - } - ] - }] - } -} - +include::../snippet/pds-param-template-metadata-syntax.json[] ---- <1> Meta data array <2> Template identifier - just as an information for logging etc. <3> Template type <4> Asset information array -<5> ID of the asset, necessary for storage download -<6> checksum of the asset ZIP file, necessary to check after download +<5> Asset identifier +<6> Name of file (inside asset) +<7> Checksum of the file (SHA256) -===== PDS asset handling +===== PDS runtime +====== Asset handling {sechub} calls {pds}, with {pds} parameter `pds.template.metadata` (syntax is described above). -The {pds} intance will fetch all wanted asset ZIP file for the current product -from storage (S3 or NFS) and extract it to `$workspaceFolder/assets/`. +The {pds} instance will fetch all defined files from storage (S3 or NFS) +and extract/copy it to `$workspaceFolder/assets/`. -Before extraction is done a checksum for the downloaded ZIP file is created and compared -with the checksum from template meta data. If it is different the {pds} job will fail -with a dedicated error message. +Before extraction is done a checksum for the downloaded file is created and compared +with the checksum from template meta data. The checksum algorithm is SHA256. +If it is different the {pds} job will fail with a dedicated error message. If the checksum is valid, the assets ZIP file will be unzipped below a subfolder with the template type: @@ -238,66 +261,74 @@ template type: An example: -The `MY_PRODUCT.zip` file contains +The `WEBSCAN_PRODUCT_ID.zip` file contains [source,text] ---- -/script.js +/script.groovy /development/ debug-settings.json ---- and the template meta data looks like this: -[source,text] +[source,json] ---- -{ - "template-metadata" : { - { - "type: "webscan-login", - "assets" : [ - { - "id" : "asset-id-1", - "sha256" : "434c6c6ec1b0ed9844149069d7d45ac18e72505b" - } - ] - } - } -} +include::../snippet/pds-param-template-metadata-example1.json[] ---- +The extraction is done into folder: +`$workspaceFolder/assets/$templateType/` which will be made available to launcher scripts (and wrapper +applications) by environmnet variable `PDS_JOB_EXTRACTED_ASSETS_FOLDER`. -It will be extracted the following way. +For the former example `WEBSCAN_PRODUCT_ID.zip` will be extracted the following way: [source,text] ---- `$workspaceFolder/ assets/ webscan-login/ - script.js - develoment/ + script.groovy + development/ debug-settings.json ---- -====== Pseudo code for usage inside PDS solutions/wrappers +====== Example code for usage inside PDS solutions/wrappers -Here an example (but pseudo code) how a product could use the assets inside: +Here an example how a product could use the assets inside +a wrapper application for a PDS solution: [source,java] ---- -WebScanTemplateData data = util.fetchWebScanTemplateData(sechubConfig); -if (data!=null){ - // folder=/$workspaceFolder/assets - File folder = util.getAssetFolder(); +import java.nio.file.*; +import com.mercedesbenz.sechub.commons.model.JSONConverter; +import com.mercedesbenz.sechub.commons.model.template.*; +// ... + +String sechubConfigAsJson = System.getenv("PDS_SCAN_CONFIGURATION"); +SecHubConfigurationModel sechubConfig = JSONConverter.get().fromJSON(SecHubConfigurationModel.class,sechubConfigAsJson); + +TemplateDataResolver resolver = new TemplateDataResolver(); + +TemplateData templateData = resolver.resolveTemplateData(TemplateType.WEBSCAN_LOGIN, sechubConfig); +if (templateData!=null){ + + Map variables = templateData.getVariables(); + + // read template script file from extracted folder + String assetExtractionFolder = System.getenv("PDS_JOB_EXTRACTED_ASSETS_FOLDER"); + Path path = Paths.get(assetExtractionFolder, TemplateType.WEBSCAN_LOGIN.getId(), "script.groovy"); + String scriptTemplate = Files.readString(path); - // in example: it is a web scan... - script = folder.getChild("webscan-login/script.js"); + // replace variable parts inside template + String script = scriptTemplate ; + script = script.replaceAll("#user", variales.get("user"); + script = script.replaceAll("#pwd", variales.get("password"); - script = replaceVariableInScript(data.getVariable("username"), script); - script = replaceVariableInScript(data.getVariable("password"), script); + // execute script + // ... - // use script.... } ---- diff --git a/sechub-doc/src/docs/asciidoc/documents/shared/snippet/pds-param-template-metadata-example1.json b/sechub-doc/src/docs/asciidoc/documents/shared/snippet/pds-param-template-metadata-example1.json new file mode 100644 index 0000000000..9f67da8c86 --- /dev/null +++ b/sechub-doc/src/docs/asciidoc/documents/shared/snippet/pds-param-template-metadata-example1.json @@ -0,0 +1,9 @@ +[ { + "templateId" : "single-singon", + "templateType" : "webscan-login", + "assetData" : { + "assetId" : "custom-webscan-setup", + "fileName" : "WEBSCAN_PRODUCT_ID.zip", + "checksum" : "434c6c6ec1b0ed9844149069d7d45ac18e72505b" + } +} ] \ No newline at end of file diff --git a/sechub-doc/src/docs/asciidoc/documents/shared/snippet/pds-param-template-metadata-syntax.json b/sechub-doc/src/docs/asciidoc/documents/shared/snippet/pds-param-template-metadata-syntax.json new file mode 100644 index 0000000000..85e400cf1b --- /dev/null +++ b/sechub-doc/src/docs/asciidoc/documents/shared/snippet/pds-param-template-metadata-syntax.json @@ -0,0 +1,13 @@ + [ //<1> + { + "templateId" : "templateId", //<2> + "templateType": "WEBSCAN_LOGIN", //<3> + + "assetData" : { //<4> + "assetId" : "assetId", //<5> + "fileName" : "fileName", //<6> + "checksum" : "fileChecksum" //<7> + } + + } + ] diff --git a/sechub-doc/src/docs/asciidoc/documents/shared/snippet/template-definition-syntax.json b/sechub-doc/src/docs/asciidoc/documents/shared/snippet/template-definition-syntax.json new file mode 100644 index 0000000000..b15de0e9fa --- /dev/null +++ b/sechub-doc/src/docs/asciidoc/documents/shared/snippet/template-definition-syntax.json @@ -0,0 +1,26 @@ +{ + "templateDefinition" : { + + "id" : "$templateId", //<1> + + "type" : "$templateType", //<2> + + "assetId" : "$assetId", //<3> + + "variables" : [ //<4> + { + "name" : "$variableName", // <5> + "optional": false, // <6> + + "validation" : { // <7> + + "minLength" : 1, // <8> + "maxLength" : 100, // <9> + + "regularExpression" : "$regularExpression" // <10> + } + } + ] + } + +} \ No newline at end of file diff --git a/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/usecase/UseCaseRestDocModelDataCollector.java b/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/usecase/UseCaseRestDocModelDataCollector.java index dd68855f7e..21c98cb548 100644 --- a/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/usecase/UseCaseRestDocModelDataCollector.java +++ b/sechub-doc/src/main/java/com/mercedesbenz/sechub/docgen/usecase/UseCaseRestDocModelDataCollector.java @@ -192,8 +192,8 @@ private File scanForSpringRestDocGenFolder(UseCaseRestDocEntry entry) { Maybe you - forgot to implement the RESTDOC test for the usecase or for one of its variants - used two differet names for the variant inside your test (annotation + code in test method) - - forgot to add the documentation calls inside a RESTDOC test, or - - you accidently used another class when calling UseCaseRestDoc.Factory.createPath(...) or + - forgot to add the documentation calls inside a RESTDOC test + - accidently used another class when calling UseCaseRestDoc.Factory.createPath(...) - executed not `gradlew sechub-doc:test` before Details: diff --git a/sechub-doc/src/test/java/com/mercedesbenz/sechub/ExampleFilesValidTest.java b/sechub-doc/src/test/java/com/mercedesbenz/sechub/ExampleFilesValidTest.java index 786c247b73..eff696a547 100644 --- a/sechub-doc/src/test/java/com/mercedesbenz/sechub/ExampleFilesValidTest.java +++ b/sechub-doc/src/test/java/com/mercedesbenz/sechub/ExampleFilesValidTest.java @@ -11,9 +11,32 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; -import com.mercedesbenz.sechub.commons.model.*; -import com.mercedesbenz.sechub.commons.model.login.*; +import com.mercedesbenz.sechub.commons.model.ClientCertificateConfiguration; +import com.mercedesbenz.sechub.commons.model.HTTPHeaderConfiguration; +import com.mercedesbenz.sechub.commons.model.JSONConverter; +import com.mercedesbenz.sechub.commons.model.JSONConverterException; +import com.mercedesbenz.sechub.commons.model.SecHubDataConfiguration; +import com.mercedesbenz.sechub.commons.model.SecHubScanConfiguration; +import com.mercedesbenz.sechub.commons.model.SecHubSourceDataConfiguration; +import com.mercedesbenz.sechub.commons.model.SecHubTimeUnit; +import com.mercedesbenz.sechub.commons.model.SecHubWebScanApiConfiguration; +import com.mercedesbenz.sechub.commons.model.SecHubWebScanApiType; +import com.mercedesbenz.sechub.commons.model.SecHubWebScanConfiguration; +import com.mercedesbenz.sechub.commons.model.WebScanDurationConfiguration; +import com.mercedesbenz.sechub.commons.model.login.Action; +import com.mercedesbenz.sechub.commons.model.login.ActionType; +import com.mercedesbenz.sechub.commons.model.login.BasicLoginConfiguration; +import com.mercedesbenz.sechub.commons.model.login.EncodingType; +import com.mercedesbenz.sechub.commons.model.login.FormLoginConfiguration; +import com.mercedesbenz.sechub.commons.model.login.Page; +import com.mercedesbenz.sechub.commons.model.login.Script; +import com.mercedesbenz.sechub.commons.model.login.TOTPHashAlgorithm; +import com.mercedesbenz.sechub.commons.model.login.WebLoginConfiguration; +import com.mercedesbenz.sechub.commons.model.login.WebLoginTOTPConfiguration; +import com.mercedesbenz.sechub.commons.model.template.TemplateType; import com.mercedesbenz.sechub.commons.pds.PDSDefaultParameterKeyConstants; +import com.mercedesbenz.sechub.commons.pds.data.PDSTemplateMetaData; +import com.mercedesbenz.sechub.commons.pds.data.PDSTemplateMetaData.PDSAssetData; import com.mercedesbenz.sechub.pds.commons.core.config.PDSProductParameterDefinition; import com.mercedesbenz.sechub.pds.commons.core.config.PDSProductParameterSetup; import com.mercedesbenz.sechub.pds.commons.core.config.PDSProductSetup; @@ -63,8 +86,8 @@ void check_pds_config_example1_can_be_loaded_and_is_valid() throws Exception { } @ParameterizedTest - @EnumSource(ExampleFile.class) - void every_sechub_config_file_is_valid(ExampleFile file) { + @EnumSource(SecHubConfigExampleFile.class) + void every_sechub_config_file_is_valid(SecHubConfigExampleFile file) { /* prepare */ String json = TestFileReader.readTextFromFile(file.getPath()); SecHubScanConfiguration config = null; @@ -81,10 +104,10 @@ void every_sechub_config_file_is_valid(ExampleFile file) { } @ParameterizedTest - @EnumSource(value = ExampleFile.class, names = { "WEBSCAN_ANONYMOUS", "WEBSCAN_BASIC_AUTH", "WEBSCAN_FORM_BASED_SCRIPT_AUTH", + @EnumSource(value = SecHubConfigExampleFile.class, names = { "WEBSCAN_ANONYMOUS", "WEBSCAN_BASIC_AUTH", "WEBSCAN_FORM_BASED_SCRIPT_AUTH", "WEBSCAN_OPENAPI_WITH_DATA_REFERENCE", "WEBSCAN_HEADER_SCAN", "WEBSCAN_CLIENT_CERTIFICATE", "WEBSCAN_FORM_BASED_SCRIPT_AUTH_WITH_TOTP" }, mode = EnumSource.Mode.INCLUDE) - void every_sechub_config_webscan_file_is_valid_and_has_a_target_uri(ExampleFile file) { + void every_sechub_config_webscan_file_is_valid_and_has_a_target_uri(SecHubConfigExampleFile file) { /* prepare */ String json = TestFileReader.readTextFromFile(file.getPath()); @@ -102,7 +125,7 @@ void every_sechub_config_webscan_file_is_valid_and_has_a_target_uri(ExampleFile @Test void webscan_anonymous_can_be_read_and_contains_expected_config() { /* prepare */ - String json = TestFileReader.readTextFromFile(ExampleFile.WEBSCAN_ANONYMOUS.getPath()); + String json = TestFileReader.readTextFromFile(SecHubConfigExampleFile.WEBSCAN_ANONYMOUS.getPath()); /* execute */ SecHubScanConfiguration config = SecHubScanConfiguration.createFromJSON(json); @@ -121,7 +144,7 @@ void webscan_anonymous_can_be_read_and_contains_expected_config() { @Test void webscan_basic_auth_can_be_read_and_contains_expected_config() { /* prepare */ - String json = TestFileReader.readTextFromFile(ExampleFile.WEBSCAN_BASIC_AUTH.getPath()); + String json = TestFileReader.readTextFromFile(SecHubConfigExampleFile.WEBSCAN_BASIC_AUTH.getPath()); /* execute */ SecHubScanConfiguration config = SecHubScanConfiguration.createFromJSON(json); @@ -144,7 +167,7 @@ void webscan_basic_auth_can_be_read_and_contains_expected_config() { @Test void webscan_form_based_script_auth_can_be_read_and_contains_expected_config() { /* prepare */ - String json = TestFileReader.readTextFromFile(ExampleFile.WEBSCAN_FORM_BASED_SCRIPT_AUTH.getPath()); + String json = TestFileReader.readTextFromFile(SecHubConfigExampleFile.WEBSCAN_FORM_BASED_SCRIPT_AUTH.getPath()); /* execute */ SecHubScanConfiguration config = SecHubScanConfiguration.createFromJSON(json); @@ -168,7 +191,7 @@ void webscan_form_based_script_auth_can_be_read_and_contains_expected_config() { @Test void webscan_openapi_with_data_reference_can_be_read_and_contains_expected_config() { /* prepare */ - String json = TestFileReader.readTextFromFile(ExampleFile.WEBSCAN_OPENAPI_WITH_DATA_REFERENCE.getPath()); + String json = TestFileReader.readTextFromFile(SecHubConfigExampleFile.WEBSCAN_OPENAPI_WITH_DATA_REFERENCE.getPath()); /* execute */ SecHubScanConfiguration config = SecHubScanConfiguration.createFromJSON(json); @@ -185,7 +208,7 @@ void webscan_openapi_with_data_reference_can_be_read_and_contains_expected_confi @Test void webscan_client_certificate_with_data_reference_can_be_read_and_contains_expected_config() { /* prepare */ - String json = TestFileReader.readTextFromFile(ExampleFile.WEBSCAN_CLIENT_CERTIFICATE_WITH_OPENAPI.getPath()); + String json = TestFileReader.readTextFromFile(SecHubConfigExampleFile.WEBSCAN_CLIENT_CERTIFICATE_WITH_OPENAPI.getPath()); /* execute */ SecHubScanConfiguration config = SecHubScanConfiguration.createFromJSON(json); @@ -205,7 +228,7 @@ void webscan_client_certificate_with_data_reference_can_be_read_and_contains_exp @Test void webscan_client_certificate_with_openapi_can_be_read_and_contains_expected_config() { /* prepare */ - String json = TestFileReader.readTextFromFile(ExampleFile.WEBSCAN_CLIENT_CERTIFICATE.getPath()); + String json = TestFileReader.readTextFromFile(SecHubConfigExampleFile.WEBSCAN_CLIENT_CERTIFICATE.getPath()); /* execute */ SecHubScanConfiguration config = SecHubScanConfiguration.createFromJSON(json); @@ -222,7 +245,7 @@ void webscan_client_certificate_with_openapi_can_be_read_and_contains_expected_c @Test void webscan_header_scan_can_be_read_and_contains_expected_config() { /* prepare */ - String json = TestFileReader.readTextFromFile(ExampleFile.WEBSCAN_HEADER_SCAN.getPath()); + String json = TestFileReader.readTextFromFile(SecHubConfigExampleFile.WEBSCAN_HEADER_SCAN.getPath()); /* execute */ SecHubScanConfiguration config = SecHubScanConfiguration.createFromJSON(json); @@ -250,7 +273,7 @@ void webscan_header_scan_can_be_read_and_contains_expected_config() { @Test void webscan_header_from_data_reference_can_be_read_and_contains_expected_config() { /* prepare */ - String json = TestFileReader.readTextFromFile(ExampleFile.WEBSCAN_HEADER_FROM_DATA_REFERENCE.getPath()); + String json = TestFileReader.readTextFromFile(SecHubConfigExampleFile.WEBSCAN_HEADER_FROM_DATA_REFERENCE.getPath()); /* execute */ SecHubScanConfiguration config = SecHubScanConfiguration.createFromJSON(json); @@ -283,7 +306,7 @@ void webscan_header_from_data_reference_can_be_read_and_contains_expected_config @Test void webscan_form_based_script_auth_with_totp_can_be_read_and_contains_expected_config() { /* prepare */ - String json = TestFileReader.readTextFromFile(ExampleFile.WEBSCAN_FORM_BASED_SCRIPT_AUTH_WITH_TOTP.getPath()); + String json = TestFileReader.readTextFromFile(SecHubConfigExampleFile.WEBSCAN_FORM_BASED_SCRIPT_AUTH_WITH_TOTP.getPath()); /* execute */ SecHubScanConfiguration config = SecHubScanConfiguration.createFromJSON(json); @@ -310,6 +333,28 @@ void webscan_form_based_script_auth_with_totp_can_be_read_and_contains_expected_ assertEquals(EncodingType.BASE64, totp.getEncodingType()); } + @Test + void pds_param_template_metadata_array_syntax_example_is_valid() { + /* prepare */ + String json = TestFileReader.readTextFromFile(PDSDataExampleFile.PDS_PARAM_TEMPLATE_META_DATA_SYNTAX.getPath()); + + /* execute */ + List result = JSONConverter.get().fromJSONtoListOf(PDSTemplateMetaData.class, json); + + /* test */ + assertEquals(1, result.size()); + PDSTemplateMetaData data = result.iterator().next(); + assertEquals("templateId", data.getTemplateId()); + assertEquals(TemplateType.WEBSCAN_LOGIN, data.getTemplateType()); + + PDSAssetData assetData = data.getAssetData(); + assertNotNull(assetData); + assertEquals("assetId", assetData.getAssetId()); + assertEquals("fileChecksum", assetData.getChecksum()); + assertEquals("fileName", assetData.getFileName()); + + } + private void assertDefaultValue(PDSProductSetup setup, boolean isMandatory, String parameterKey, String expectedDefault) { PDSProductParameterSetup parameters = setup.getParameters(); List list = null; diff --git a/sechub-doc/src/test/java/com/mercedesbenz/sechub/PDSDataExampleFile.java b/sechub-doc/src/test/java/com/mercedesbenz/sechub/PDSDataExampleFile.java new file mode 100644 index 0000000000..b4dc4be87f --- /dev/null +++ b/sechub-doc/src/test/java/com/mercedesbenz/sechub/PDSDataExampleFile.java @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub; + +public enum PDSDataExampleFile { + + PDS_PARAM_TEMPLATE_META_DATA_SYNTAX("src/docs/asciidoc/documents/shared/snippet/pds-param-template-metadata-syntax.json"); + ; + + private String path; + + private PDSDataExampleFile(String path) { + this.path = path; + } + + public String getPath() { + return path; + } +} diff --git a/sechub-doc/src/test/java/com/mercedesbenz/sechub/ExampleFile.java b/sechub-doc/src/test/java/com/mercedesbenz/sechub/SecHubConfigExampleFile.java similarity index 96% rename from sechub-doc/src/test/java/com/mercedesbenz/sechub/ExampleFile.java rename to sechub-doc/src/test/java/com/mercedesbenz/sechub/SecHubConfigExampleFile.java index 36bac1cf81..95ae922289 100644 --- a/sechub-doc/src/test/java/com/mercedesbenz/sechub/ExampleFile.java +++ b/sechub-doc/src/test/java/com/mercedesbenz/sechub/SecHubConfigExampleFile.java @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub; -public enum ExampleFile { +public enum SecHubConfigExampleFile { DATA_SECTION_EXAMPLE_1("src/docs/asciidoc/documents/shared/configuration/sechub_config_data_section_general_example1.json"), @@ -45,7 +45,7 @@ public enum ExampleFile { private String path; - private ExampleFile(String path) { + private SecHubConfigExampleFile(String path) { this.path = path; } diff --git a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/AssetRestControllerRestDocTest.java b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/AssetRestControllerRestDocTest.java new file mode 100644 index 0000000000..34fb89c4bc --- /dev/null +++ b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/AssetRestControllerRestDocTest.java @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.restdoc; + +import static com.mercedesbenz.sechub.commons.core.CommonConstants.*; +import static com.mercedesbenz.sechub.restdoc.RestDocumentation.*; +import static com.mercedesbenz.sechub.test.RestDocPathParameter.*; +import static com.mercedesbenz.sechub.test.SecHubTestURLBuilder.*; +import static org.mockito.Mockito.*; +import static org.springframework.restdocs.headers.HeaderDocumentation.*; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.request.RequestDocumentation.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.io.InputStream; +import java.lang.annotation.Annotation; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import com.mercedesbenz.sechub.docgen.util.RestDocFactory; +import com.mercedesbenz.sechub.docgen.util.RestDocTestFileSupport; +import com.mercedesbenz.sechub.domain.scan.asset.AssetDetailData; +import com.mercedesbenz.sechub.domain.scan.asset.AssetFileData; +import com.mercedesbenz.sechub.domain.scan.asset.AssetFileRepository; +import com.mercedesbenz.sechub.domain.scan.asset.AssetRestController; +import com.mercedesbenz.sechub.domain.scan.asset.AssetService; +import com.mercedesbenz.sechub.sharedkernel.Profiles; +import com.mercedesbenz.sechub.sharedkernel.logging.AuditLogService; +import com.mercedesbenz.sechub.sharedkernel.logging.LogSanitizer; +import com.mercedesbenz.sechub.sharedkernel.security.RoleConstants; +import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseRestDoc; +import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseRestDoc.SpringRestDocOutput; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminDeletesAssetCompletely; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminDeletesOneFileFromAsset; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminDownloadsAssetFile; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminFetchesAssetDetails; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminFetchesAssetIds; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminUploadsAssetFile; +import com.mercedesbenz.sechub.test.ExampleConstants; +import com.mercedesbenz.sechub.test.TestIsNecessaryForDocumentation; +import com.mercedesbenz.sechub.test.TestPortProvider; + +@RunWith(SpringRunner.class) +@WebMvcTest +@ActiveProfiles({ Profiles.TEST, Profiles.ADMIN_ACCESS }) +@WithMockUser(roles = RoleConstants.ROLE_SUPERADMIN) +@ContextConfiguration(classes = { AssetRestController.class, AssetRestControllerRestDocTest.class }) +@Import(TestRestDocSecurityConfiguration.class) +@AutoConfigureRestDocs(uriScheme = "https", uriHost = ExampleConstants.URI_SECHUB_SERVER, uriPort = 443) +public class AssetRestControllerRestDocTest implements TestIsNecessaryForDocumentation { + + private static final String TEST_CHECKSUM1 = "c6965634c4ec8e4f5e72dffd36ea725860e8b485216260264a0973073805e422"; + + private static final int PORT_USED = TestPortProvider.DEFAULT_INSTANCE.getRestDocTestPort(); + + @Autowired + private MockMvc mockMvc; + + @MockBean + private AssetFileRepository assetFileRepository; + + @MockBean + AssetService assetService; + + @MockBean + AuditLogService auditLogService; + + @MockBean + LogSanitizer logSanitizer; + + private static final String TEST_ASSET_ID1 = "asset-1"; + private static final String TEST_ASSET_ID2 = "asset-2"; + + private static final String TEST_FILENAME1 = "PRODUCT1.zip"; + + @Before + public void before() { + } + + @Test + @UseCaseRestDoc(useCase = UseCaseAdminDeletesOneFileFromAsset.class) + public void restdoc_admin_deletes_one_file_from_asset() throws Exception { + /* prepare */ + String apiEndpoint = https(PORT_USED).buildAdminDeletesAssetFile(ASSET_ID.pathElement(), FILE_NAME.pathElement()); + Class useCase = UseCaseAdminDeletesOneFileFromAsset.class; + + /* execute + test @formatter:off */ + this.mockMvc.perform( + delete(apiEndpoint, TEST_ASSET_ID1, TEST_FILENAME1). + header(AuthenticationHelper.HEADER_NAME, AuthenticationHelper.getHeaderValue()) + ). + andExpect(status().isOk()). + andDo(defineRestService(). + with(). + useCaseData(useCase). + tag(RestDocFactory.extractTag(apiEndpoint)). + requestSchema(OpenApiSchema.ASSETS.getSchema()). + and(). + document( + pathParameters( + parameterWithName(ASSET_ID.paramName()).description("The asset identifier"), + parameterWithName(FILE_NAME.paramName()).description("The name of the file to delete inside the asset") + ) + )); + + /* @formatter:on */ + } + + @Test + @UseCaseRestDoc(useCase = UseCaseAdminDeletesAssetCompletely.class) + public void restdoc_admin_deletes_asset_completely() throws Exception { + /* prepare */ + String apiEndpoint = https(PORT_USED).buildAdminDeletesAsset(ASSET_ID.pathElement()); + Class useCase = UseCaseAdminDeletesAssetCompletely.class; + + /* execute + test @formatter:off */ + this.mockMvc.perform( + delete(apiEndpoint, TEST_ASSET_ID1). + contentType(MediaType.APPLICATION_JSON_VALUE). + header(AuthenticationHelper.HEADER_NAME, AuthenticationHelper.getHeaderValue()) + ). + andExpect(status().isOk()). + andDo(defineRestService(). + with(). + useCaseData(useCase). + tag(RestDocFactory.extractTag(apiEndpoint)). + requestSchema(OpenApiSchema.ASSETS.getSchema()). + and(). + document( + pathParameters( + parameterWithName(ASSET_ID.paramName()).description("The asset identifier for the asset which shall be deleted completely") + ) + )); + + /* @formatter:on */ + } + + @Test + @UseCaseRestDoc(useCase = UseCaseAdminFetchesAssetIds.class) + public void restdoc_admin_fetches_all_asset_ids() throws Exception { + /* prepare */ + when(assetService.fetchAllAssetIds()).thenReturn(List.of(TEST_ASSET_ID1, TEST_ASSET_ID2)); + + String apiEndpoint = https(PORT_USED).buildAdminFetchesAllAssetIds(); + Class useCase = UseCaseAdminFetchesAssetIds.class; + + /* execute + test @formatter:off */ + this.mockMvc.perform( + get(apiEndpoint). + header(AuthenticationHelper.HEADER_NAME, AuthenticationHelper.getHeaderValue()) + ). + andExpect(status().isOk()). + andDo(defineRestService(). + with(). + useCaseData(useCase). + tag(RestDocFactory.extractTag(apiEndpoint)). + requestSchema(OpenApiSchema.ASSETS.getSchema()). + and(). + document( + responseFields( + fieldWithPath("[]").description("Array contains all existing asset identifiers") + ) + )); + + /* @formatter:on */ + } + + @Test + @UseCaseRestDoc(useCase = UseCaseAdminFetchesAssetDetails.class) + public void restdoc_admin_fetches_asset_details() throws Exception { + AssetDetailData asset1Details = new AssetDetailData(); + asset1Details.setAssetId(TEST_ASSET_ID1); + AssetFileData fileInfo = new AssetFileData(); + fileInfo.setChecksum(TEST_CHECKSUM1); + fileInfo.setFileName(TEST_FILENAME1); + asset1Details.getFiles().add(fileInfo); + /* prepare */ + when(assetService.fetchAssetDetails(TEST_ASSET_ID1)).thenReturn(asset1Details); + + String apiEndpoint = https(PORT_USED).buildAdminFetchesAssetDetails(ASSET_ID.pathElement()); + Class useCase = UseCaseAdminFetchesAssetDetails.class; + + /* execute + test @formatter:off */ + this.mockMvc.perform( + get(apiEndpoint, TEST_ASSET_ID1). + contentType(MediaType.APPLICATION_JSON_VALUE). + header(AuthenticationHelper.HEADER_NAME, AuthenticationHelper.getHeaderValue()) + ). + andExpect(status().isOk()). + andDo(defineRestService(). + with(). + useCaseData(useCase). + tag(RestDocFactory.extractTag(apiEndpoint)). + requestSchema(OpenApiSchema.ASSETS.getSchema()). + and(). + document( + responseFields( + fieldWithPath("assetId").description("The asset identifier"), + fieldWithPath("files[]").description("Array containing data about files from asset"), + fieldWithPath("files[].fileName").description("Name of file"), + fieldWithPath("files[].checksum").description("Checksum for file") + ) + )); + + /* @formatter:on */ + } + + @Test + @UseCaseRestDoc(useCase = UseCaseAdminUploadsAssetFile.class) + public void restDoc_admin_uploads_assetfile() throws Exception { + /* prepare */ + String apiEndpoint = https(PORT_USED).buildAdminUploadsAssetFile(ASSET_ID.pathElement()); + Class useCase = UseCaseAdminUploadsAssetFile.class; + + InputStream inputStreamTo = RestDocTestFileSupport.getTestfileSupport().getInputStreamTo("upload/zipfile_contains_only_test1.txt.zip"); + MockMultipartFile file1 = new MockMultipartFile("file", inputStreamTo); + /* execute + test @formatter:off */ + this.mockMvc.perform( + multipart(apiEndpoint, TEST_ASSET_ID1). + file(file1). + queryParam(MULTIPART_CHECKSUM, TEST_CHECKSUM1) + ). + andExpect(status().isOk()). + andDo(defineRestService(). + with(). + useCaseData(useCase). + tag(RestDocFactory.extractTag(apiEndpoint)). + and(). + document( + requestHeaders( + ), + pathParameters( + parameterWithName(ASSET_ID.paramName()).description("The id of the asset to which the uploaded file belongs to") + ), + queryParameters( + parameterWithName(MULTIPART_CHECKSUM).description("A sha256 checksum for file upload validation") + ), + requestParts( + partWithName(MULTIPART_FILE).description("The asset file to upload") + ) + )); + + /* @formatter:on */ + } + + @Test + @UseCaseRestDoc(useCase = UseCaseAdminDownloadsAssetFile.class, wanted = { SpringRestDocOutput.PATH_PARAMETERS, SpringRestDocOutput.REQUEST_FIELDS, + SpringRestDocOutput.CURL_REQUEST }) + public void restdoc_admin_downloads_assetfile() throws Exception { + /* prepare */ + String apiEndpoint = https(PORT_USED).buildAdminDownloadsAssetFile(ASSET_ID.pathElement(), FILE_NAME.pathElement()); + Class useCase = UseCaseAdminDownloadsAssetFile.class; + + /* execute + test @formatter:off */ + this.mockMvc.perform( + get(apiEndpoint,TEST_ASSET_ID1, TEST_FILENAME1). + contentType(MediaType.APPLICATION_JSON_VALUE). + header(AuthenticationHelper.HEADER_NAME, AuthenticationHelper.getHeaderValue()) + ). + andExpect(status().isOk()). + andDo(defineRestService(). + with(). + useCaseData(useCase). + tag(RestDocFactory.extractTag(apiEndpoint)). + responseSchema(OpenApiSchema.ASSETS.getSchema()). + and(). + document( + requestHeaders( + + ), + pathParameters( + parameterWithName(ASSET_ID.paramName()).description("The asset identifier"), + parameterWithName(FILE_NAME.paramName()).description("The name of the file to download from asset") + ) + )); + + /* @formatter:on */ + } + +} diff --git a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/FalsePositiveRestControllerRestDocTest.java b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/FalsePositiveRestControllerRestDocTest.java index 2c141220ad..a77d3c65d9 100644 --- a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/FalsePositiveRestControllerRestDocTest.java +++ b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/FalsePositiveRestControllerRestDocTest.java @@ -157,7 +157,7 @@ public void restdoc_mark_false_positives() throws Exception { fieldWithPath(PROPERTY_JOBDATA+"[]."+ PROPERTY_FINDINGID).description("SecHub finding identifier - identifies problem inside the job which shall be markeda as a false positive."), fieldWithPath(PROPERTY_JOBDATA+"[]."+ FalsePositiveJobData.PROPERTY_COMMENT).optional().description("A comment describing why this is a false positive"), - fieldWithPath(PROPERTY_PROJECTDATA).description("Porject data list containing false positive setup for the project"), + fieldWithPath(PROPERTY_PROJECTDATA).description("Project data list containing false positive setup for the project"), fieldWithPath(PROPERTY_PROJECTDATA+"[]."+ PROPERTY_ID).description("Identifier which is used to update or remove the respective false positive entry."), fieldWithPath(PROPERTY_PROJECTDATA+"[]."+ FalsePositiveProjectData.PROPERTY_COMMENT).optional().description("A comment describing why this is a false positive."), fieldWithPath(PROPERTY_PROJECTDATA+"[]."+ PROPERTY_WEBSCAN).optional().description("Defines a section for false positives which occur during webscans."), @@ -352,7 +352,7 @@ public void user_fetches_false_positive_configuration() throws Exception { fieldWithPath(PROPERTY_FALSE_POSITIVES+"[]."+FalsePositiveEntry.PROPERTY_JOBDATA+"."+PROPERTY_FINDINGID).description("SecHub finding identifier - identifies problem inside the job which shall be markeda as a false positive. *ATTENTION*: at the moment only code scan false positive handling is supported. Infra and web scan findings will lead to a non accepted error!"), fieldWithPath(PROPERTY_FALSE_POSITIVES+"[]."+FalsePositiveEntry.PROPERTY_JOBDATA+"."+FalsePositiveJobData.PROPERTY_COMMENT).optional().description("A comment from author describing why this was marked as a false positive"), - fieldWithPath(PROPERTY_FALSE_POSITIVES+"[]."+PROPERTY_PROJECTDATA).optional().description("Porject data list containing false positive setup for the project."), + fieldWithPath(PROPERTY_FALSE_POSITIVES+"[]."+PROPERTY_PROJECTDATA).optional().description("Project data list containing false positive setup for the project."), fieldWithPath(PROPERTY_FALSE_POSITIVES+"[]."+PROPERTY_PROJECTDATA+"."+ PROPERTY_ID).description("Identifier which is used to update or remove the respective false positive entry."), fieldWithPath(PROPERTY_FALSE_POSITIVES+"[]."+PROPERTY_PROJECTDATA+"."+ FalsePositiveProjectData.PROPERTY_COMMENT).optional().description("A comment describing why this is a false positive."), fieldWithPath(PROPERTY_FALSE_POSITIVES+"[]."+PROPERTY_PROJECTDATA+"."+ PROPERTY_WEBSCAN).optional().description("Defines a section for false positives which occur during webscans."), diff --git a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/OpenApiSchema.java b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/OpenApiSchema.java index 70ac1df00c..b2ddd75914 100644 --- a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/OpenApiSchema.java +++ b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/OpenApiSchema.java @@ -64,6 +64,10 @@ enum OpenApiSchema { ENCRYPTION_STATUS("EncryptionStatus"), + TEMPLATES("Templates"), + + ASSETS("Assets"), + ; private final Schema schema; diff --git a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/ProjectAdministrationRestControllerRestDocTest.java b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/ProjectAdministrationRestControllerRestDocTest.java index ef38fd9788..bcf9ba4b80 100644 --- a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/ProjectAdministrationRestControllerRestDocTest.java +++ b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/ProjectAdministrationRestControllerRestDocTest.java @@ -1,25 +1,17 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.restdoc; -import static com.mercedesbenz.sechub.restdoc.RestDocumentation.defineRestService; -import static com.mercedesbenz.sechub.test.RestDocPathParameter.PROJECT_ACCESS_LEVEL; -import static com.mercedesbenz.sechub.test.RestDocPathParameter.PROJECT_ID; -import static com.mercedesbenz.sechub.test.RestDocPathParameter.USER_ID; -import static com.mercedesbenz.sechub.test.SecHubTestURLBuilder.https; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; -import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete; -import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; -import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; -import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; -import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; -import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; -import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; -import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static com.mercedesbenz.sechub.restdoc.RestDocumentation.*; +import static com.mercedesbenz.sechub.test.RestDocPathParameter.*; +import static com.mercedesbenz.sechub.test.SecHubTestURLBuilder.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.springframework.restdocs.headers.HeaderDocumentation.*; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.request.RequestDocumentation.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import java.lang.annotation.Annotation; import java.net.URI; @@ -62,6 +54,7 @@ import com.mercedesbenz.sechub.domain.administration.project.ProjectJsonInput.ProjectWhiteList; import com.mercedesbenz.sechub.domain.administration.project.ProjectMetaDataEntity; import com.mercedesbenz.sechub.domain.administration.project.ProjectRepository; +import com.mercedesbenz.sechub.domain.administration.project.ProjectTemplateService; import com.mercedesbenz.sechub.domain.administration.project.ProjectUnassignUserService; import com.mercedesbenz.sechub.domain.administration.project.ProjectUpdateWhitelistService; import com.mercedesbenz.sechub.domain.administration.user.User; @@ -70,6 +63,8 @@ import com.mercedesbenz.sechub.sharedkernel.project.ProjectAccessLevel; import com.mercedesbenz.sechub.sharedkernel.security.RoleConstants; import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseRestDoc; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminAssignsTemplateToProject; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminUnassignsTemplateFromProject; import com.mercedesbenz.sechub.sharedkernel.usecases.admin.project.UseCaseAdminChangesProjectAccessLevel; import com.mercedesbenz.sechub.sharedkernel.usecases.admin.project.UseCaseAdminChangesProjectDescription; import com.mercedesbenz.sechub.sharedkernel.usecases.admin.project.UseCaseAdminCreatesProject; @@ -133,6 +128,9 @@ public class ProjectAdministrationRestControllerRestDocTest implements TestIsNec @MockBean ProjectChangeAccessLevelService projectChangeAccessLevelService; + @MockBean + ProjectTemplateService projectTemplateService; + @Before public void before() { when(createProjectInputvalidator.supports(ProjectJsonInput.class)).thenReturn(true); @@ -244,7 +242,7 @@ public void restdoc_delete_project() throws Exception { ), pathParameters( - parameterWithName(PROJECT_ID.paramName()).description("The id for project to delete") + parameterWithName(PROJECT_ID.paramName()).description("The project id to delete") ) )); /* @formatter:on */ @@ -274,7 +272,7 @@ public void restdoc_change_project_owner() throws Exception { ), pathParameters( - parameterWithName(PROJECT_ID.paramName()).description("The id for project"), + parameterWithName(PROJECT_ID.paramName()).description("The project id"), parameterWithName(USER_ID.paramName()).description("The user id of the user to assign to project as the owner") ) )); @@ -322,7 +320,7 @@ public void restdoc_change_project_access_level() throws Exception { ), pathParameters( - parameterWithName(PROJECT_ID.paramName()).description("The id for project"), + parameterWithName(PROJECT_ID.paramName()).description("The project id"), parameterWithName(PROJECT_ACCESS_LEVEL.paramName()).description("The new project access level. "+acceptedValues) ) )); @@ -354,7 +352,7 @@ public void restdoc_assign_user2project() throws Exception { ), pathParameters( - parameterWithName(PROJECT_ID.paramName()).description("The id for project"), + parameterWithName(PROJECT_ID.paramName()).description("The project id"), parameterWithName(USER_ID.paramName()).description("The user id of the user to assign to project") ) )); @@ -386,7 +384,7 @@ public void restdoc_unassign_userFromProject() throws Exception { ), pathParameters( - parameterWithName(PROJECT_ID.paramName()).description("The id for project"), + parameterWithName(PROJECT_ID.paramName()).description("The project id"), parameterWithName(USER_ID.paramName()).description("The user id of the user to unassign from project") ) )); @@ -454,13 +452,14 @@ public void restdoc_show_project_details() throws Exception { ), pathParameters( - parameterWithName(PROJECT_ID.paramName()).description("The id for project to show details for") + parameterWithName(PROJECT_ID.paramName()).description("The project id to show details for") ), responseFields( fieldWithPath(ProjectDetailInformation.PROPERTY_PROJECT_ID).description("The name of the project"), fieldWithPath(ProjectDetailInformation.PROPERTY_USERS).description("A list of all users having access to the project"), fieldWithPath(ProjectDetailInformation.PROPERTY_OWNER).description("Username of the owner of this project. An owner is the person in charge."), fieldWithPath(ProjectDetailInformation.PROPERTY_WHITELIST).description("A list of all whitelisted URIs. Only these ones can be scanned for the project!"), + fieldWithPath(ProjectDetailInformation.PROPERTY_TEMPLATE_IDS).description("A list of all templates assigned to the project"), fieldWithPath(ProjectDetailInformation.PROPERTY_METADATA).description("An JSON object containing metadata key-value pairs defined for this project."), fieldWithPath(ProjectDetailInformation.PROPERTY_METADATA + ".key1").description("An arbitrary metadata key"), fieldWithPath(ProjectDetailInformation.PROPERTY_ACCESSLEVEL).description("The project access level"), @@ -533,15 +532,17 @@ public void restdoc_change_project_description() throws Exception { ), pathParameters( - parameterWithName(PROJECT_ID.paramName()).description("The id for project to change details for") + parameterWithName(PROJECT_ID.paramName()).description("The project id to change details for") ), responseFields( fieldWithPath(ProjectDetailInformation.PROPERTY_PROJECT_ID).description("The name of the project."), fieldWithPath(ProjectDetailInformation.PROPERTY_USERS).description("A list of all users having access to the project."), fieldWithPath(ProjectDetailInformation.PROPERTY_OWNER).description("Username of the owner of this project. An owner is the person in charge."), fieldWithPath(ProjectDetailInformation.PROPERTY_WHITELIST).description("A list of all whitelisted URIs. Only these ones can be scanned for the project!"), + fieldWithPath(ProjectDetailInformation.PROPERTY_TEMPLATE_IDS).description("A list of all templates assigned to the project"), fieldWithPath(ProjectDetailInformation.PROPERTY_METADATA).description("An JSON object containing metadata key-value pairs defined for this project."), - fieldWithPath(ProjectDetailInformation.PROPERTY_METADATA + ".key1").description("An arbitrary metadata key."), + fieldWithPath(ProjectDetailInformation.PROPERTY_METADATA + ".key1").description("An arbitrary metadata key"), + fieldWithPath(ProjectDetailInformation.PROPERTY_ACCESSLEVEL).description("The project access level"), fieldWithPath(ProjectDetailInformation.PROPERTY_ACCESSLEVEL).description("The project access level"), fieldWithPath(ProjectDetailInformation.PROPERTY_DESCRIPTION).description("The project description.") ) @@ -550,4 +551,68 @@ public void restdoc_change_project_description() throws Exception { /* @formatter:on */ } + @Test + @UseCaseRestDoc(useCase = UseCaseAdminAssignsTemplateToProject.class) + public void restdoc_assign_template2project() throws Exception { + /* prepare */ + String apiEndpoint = https(PORT_USED).buildAdminAssignsTemplateToProjectUrl(TEMPLATE_ID.pathElement(), PROJECT_ID.pathElement()); + Class useCase = UseCaseAdminAssignsTemplateToProject.class; + + /* execute + test @formatter:off */ + mockMvc.perform( + put(apiEndpoint, "projectId1", "template1"). + contentType(MediaType.APPLICATION_JSON_VALUE). + header(AuthenticationHelper.HEADER_NAME, AuthenticationHelper.getHeaderValue()) + ). + andExpect(status().isOk()). + andDo(defineRestService(). + with(). + useCaseData(useCase). + tag(RestDocFactory.extractTag(apiEndpoint)). + and(). + document( + requestHeaders( + + ), + pathParameters( + parameterWithName(PROJECT_ID.paramName()).description("The project id"), + parameterWithName(TEMPLATE_ID.paramName()).description("The id of the template to assign to project") + ) + )); + + /* @formatter:on */ + } + + @Test + @UseCaseRestDoc(useCase = UseCaseAdminUnassignsTemplateFromProject.class) + public void restdoc_unassign_templateFromproject() throws Exception { + /* prepare */ + String apiEndpoint = https(PORT_USED).buildAdminUnAssignsTemplateToProjectUrl(TEMPLATE_ID.pathElement(), PROJECT_ID.pathElement()); + Class useCase = UseCaseAdminUnassignsTemplateFromProject.class; + + /* execute + test @formatter:off */ + mockMvc.perform( + delete(apiEndpoint, "projectId1", "template1"). + contentType(MediaType.APPLICATION_JSON_VALUE). + header(AuthenticationHelper.HEADER_NAME, AuthenticationHelper.getHeaderValue()) + ). + andExpect(status().isOk()). + andDo(defineRestService(). + with(). + useCaseData(useCase). + tag(RestDocFactory.extractTag(apiEndpoint)). + and(). + document( + requestHeaders( + + ), + pathParameters( + parameterWithName(PROJECT_ID.paramName()).description("The project id"), + parameterWithName(TEMPLATE_ID.paramName()).description("The id of the template to unassign from project") + ) + )); + + /* @formatter:on */ + } + } diff --git a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/TemplateRestControllerRestDocTest.java b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/TemplateRestControllerRestDocTest.java new file mode 100644 index 0000000000..f9e84c6f30 --- /dev/null +++ b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/TemplateRestControllerRestDocTest.java @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.restdoc; + +import static com.mercedesbenz.sechub.commons.model.template.TemplateDefinition.*; +import static com.mercedesbenz.sechub.restdoc.RestDocumentation.*; +import static com.mercedesbenz.sechub.test.RestDocPathParameter.*; +import static com.mercedesbenz.sechub.test.SecHubTestURLBuilder.*; +import static org.mockito.Mockito.*; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.request.RequestDocumentation.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.lang.annotation.Annotation; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.test.web.servlet.MockMvc; + +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition; +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition.TemplateVariable; +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition.TemplateVariableValidation; +import com.mercedesbenz.sechub.commons.model.template.TemplateType; +import com.mercedesbenz.sechub.docgen.util.RestDocFactory; +import com.mercedesbenz.sechub.domain.scan.template.TemplateRepository; +import com.mercedesbenz.sechub.domain.scan.template.TemplateRestController; +import com.mercedesbenz.sechub.domain.scan.template.TemplateService; +import com.mercedesbenz.sechub.sharedkernel.Profiles; +import com.mercedesbenz.sechub.sharedkernel.logging.AuditLogService; +import com.mercedesbenz.sechub.sharedkernel.logging.LogSanitizer; +import com.mercedesbenz.sechub.sharedkernel.security.RoleConstants; +import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseRestDoc; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminCreatesOrUpdatesTemplate; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminDeletesTemplate; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminFetchesAllTemplateIds; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminFetchesTemplate; +import com.mercedesbenz.sechub.test.ExampleConstants; +import com.mercedesbenz.sechub.test.TestIsNecessaryForDocumentation; +import com.mercedesbenz.sechub.test.TestPortProvider; + +@RunWith(SpringRunner.class) +@WebMvcTest(TemplateRestController.class) +@ContextConfiguration(classes = { TemplateRestController.class, TemplateRestControllerRestDocTest.class }) +@WithMockUser(roles = RoleConstants.ROLE_SUPERADMIN) +@ActiveProfiles({ Profiles.TEST, Profiles.ADMIN_ACCESS }) +@Import(TestRestDocSecurityConfiguration.class) +@AutoConfigureRestDocs(uriScheme = "https", uriHost = ExampleConstants.URI_SECHUB_SERVER, uriPort = 443) +public class TemplateRestControllerRestDocTest implements TestIsNecessaryForDocumentation { + + private static final int PORT_USED = TestPortProvider.DEFAULT_INSTANCE.getRestDocTestPort(); + + @Autowired + private MockMvc mockMvc; + + @MockBean + private TemplateRepository templateRepository; + + @MockBean + TemplateService templateService; + + @MockBean + AuditLogService auditLogService; + + @MockBean + LogSanitizer logSanitizer; + + private TemplateDefinition definition; + + private TemplateVariable usernameVariable; + + private TemplateVariable passwordVariable; + + private static final String TEST_TEMPLATE_ID1 = "template1"; + private static final String TEST_TEMPLATE_ID2 = "template2"; + + @Before + public void before() { + definition = new TemplateDefinition(); + definition.setType(TemplateType.WEBSCAN_LOGIN); + definition.setAssetId("asset-id1"); + + usernameVariable = new TemplateVariable(); + usernameVariable.setName("username"); + TemplateVariableValidation usernameValidation = new TemplateVariableValidation(); + usernameValidation.setMinLength(3); + usernameValidation.setMaxLength(15); + usernameValidation.setRegularExpression("[a-zA-Z0-9_-].*"); + + usernameVariable.setValidation(usernameValidation); + + passwordVariable = new TemplateVariable(); + passwordVariable.setName("password"); + TemplateVariableValidation passwordValidation = new TemplateVariableValidation(); + passwordValidation.setMaxLength(20); + + passwordVariable.setValidation(passwordValidation); + + definition.getVariables().add(usernameVariable); + } + + @Test + @UseCaseRestDoc(useCase = UseCaseAdminCreatesOrUpdatesTemplate.class) + public void restdoc_admin_creates_or_updates_template() throws Exception { + /* prepare */ + String apiEndpoint = https(PORT_USED).buildAdminCreatesOrUpdatesTemplate(TEMPLATE_ID.pathElement()); + Class useCase = UseCaseAdminCreatesOrUpdatesTemplate.class; + + String content = definition.toFormattedJSON(); + + /* execute + test @formatter:off */ + this.mockMvc.perform( + put(apiEndpoint, TEST_TEMPLATE_ID1). + contentType(MediaType.APPLICATION_JSON_VALUE). + content(content). + header(AuthenticationHelper.HEADER_NAME, AuthenticationHelper.getHeaderValue()) + ). + andExpect(status().isOk()). + andDo(defineRestService(). + with(). + useCaseData(useCase). + tag(RestDocFactory.extractTag(apiEndpoint)). + requestSchema(OpenApiSchema.TEMPLATES.getSchema()). + and(). + document( + requestFields( + fieldWithPath(PROPERTY_TYPE).description("The template type. Must be be defined when a new template is created. An update will ignore changes of this property because the type is immutable! Currently supported types are: "+ TemplateType.values()), + + fieldWithPath(PROPERTY_ASSET_ID).description("The asset id used by the template"), + fieldWithPath(PROPERTY_VARIABLES+"[]."+ TemplateVariable.PROPERTY_NAME).description("The variable name"), + fieldWithPath(PROPERTY_VARIABLES+"[]."+ TemplateVariable.PROPERTY_OPTIONAL).optional().description("Defines if the variable is optional. The default is false"), + fieldWithPath(PROPERTY_VARIABLES+"[]."+ TemplateVariable.PROPERTY_VALIDATION).optional().description("Defines a simple validation segment."), + fieldWithPath(PROPERTY_VARIABLES+"[]."+ TemplateVariable.PROPERTY_VALIDATION+"."+ TemplateVariableValidation.PROPERTY_MIN_LENGTH).optional().description("The minimum content length of this variable"), + fieldWithPath(PROPERTY_VARIABLES+"[]."+ TemplateVariable.PROPERTY_VALIDATION+"."+ TemplateVariableValidation.PROPERTY_MAX_LENGTH).optional().description("The maximum content length of this variable"), + fieldWithPath(PROPERTY_VARIABLES+"[]."+ TemplateVariable.PROPERTY_VALIDATION+"."+ TemplateVariableValidation.PROPERTY_REGULAR_EXPRESSION).optional().description("A regular expression which must match to accept the user input inside the variable") + ), + pathParameters( + parameterWithName(TEMPLATE_ID.paramName()).description("The (unique) template id") + ) + )); + + /* @formatter:on */ + } + + @Test + @UseCaseRestDoc(useCase = UseCaseAdminDeletesTemplate.class) + public void restdoc_admin_deletes_template() throws Exception { + /* prepare */ + String apiEndpoint = https(PORT_USED).buildAdminDeletesTemplate(TEMPLATE_ID.pathElement()); + Class useCase = UseCaseAdminDeletesTemplate.class; + + /* execute + test @formatter:off */ + this.mockMvc.perform( + delete(apiEndpoint, TEST_TEMPLATE_ID1). + contentType(MediaType.APPLICATION_JSON_VALUE). + header(AuthenticationHelper.HEADER_NAME, AuthenticationHelper.getHeaderValue()) + ). + andExpect(status().isOk()). + andDo(defineRestService(). + with(). + useCaseData(useCase). + tag(RestDocFactory.extractTag(apiEndpoint)). + requestSchema(OpenApiSchema.TEMPLATES.getSchema()). + and(). + document( + pathParameters( + parameterWithName(TEMPLATE_ID.paramName()).description("The (unique) template id") + ) + )); + + /* @formatter:on */ + } + + @Test + @UseCaseRestDoc(useCase = UseCaseAdminFetchesTemplate.class) + public void restdoc_admin_fetches_template() throws Exception { + /* prepare */ + definition.setId(TEST_TEMPLATE_ID1); // to have this in result as well, for create/delete it was not necessary, but + // here we want it + when(templateService.fetchTemplateDefinition(TEST_TEMPLATE_ID1)).thenReturn(definition); + + String apiEndpoint = https(PORT_USED).buildAdminFetchesTemplate(TEMPLATE_ID.pathElement()); + Class useCase = UseCaseAdminFetchesTemplate.class; + + /* execute + test @formatter:off */ + this.mockMvc.perform( + get(apiEndpoint, TEST_TEMPLATE_ID1). + contentType(MediaType.APPLICATION_JSON_VALUE). + header(AuthenticationHelper.HEADER_NAME, AuthenticationHelper.getHeaderValue()) + ). + andExpect(status().isOk()). + andDo(defineRestService(). + with(). + useCaseData(useCase). + tag(RestDocFactory.extractTag(apiEndpoint)). + requestSchema(OpenApiSchema.TEMPLATES.getSchema()). + and(). + document( + responseFields( + fieldWithPath(PROPERTY_TYPE).description("The template type. Currently supported types are: "+ TemplateType.values()), + + fieldWithPath(PROPERTY_ID).description("The (unique) template id"), + fieldWithPath(PROPERTY_ASSET_ID).description("The asset id used by the template"), + fieldWithPath(PROPERTY_VARIABLES+"[]."+ TemplateVariable.PROPERTY_NAME).description("The variable name"), + fieldWithPath(PROPERTY_VARIABLES+"[]."+ TemplateVariable.PROPERTY_OPTIONAL).optional().description("Defines if the variable is optional. The default is false"), + fieldWithPath(PROPERTY_VARIABLES+"[]."+ TemplateVariable.PROPERTY_VALIDATION).optional().description("Defines a simple validation segment."), + fieldWithPath(PROPERTY_VARIABLES+"[]."+ TemplateVariable.PROPERTY_VALIDATION+"."+ TemplateVariableValidation.PROPERTY_MIN_LENGTH).optional().description("The minimum content length of this variable"), + fieldWithPath(PROPERTY_VARIABLES+"[]."+ TemplateVariable.PROPERTY_VALIDATION+"."+ TemplateVariableValidation.PROPERTY_MAX_LENGTH).optional().description("The maximum content length of this variable"), + fieldWithPath(PROPERTY_VARIABLES+"[]."+ TemplateVariable.PROPERTY_VALIDATION+"."+ TemplateVariableValidation.PROPERTY_REGULAR_EXPRESSION).optional().description("A regular expression which must match to accept the user input inside the variable") + ), + pathParameters( + parameterWithName(TEMPLATE_ID.paramName()).description("The (unique) template id") + ) + )); + + /* @formatter:on */ + } + + @Test + @UseCaseRestDoc(useCase = UseCaseAdminFetchesAllTemplateIds.class) + public void restdoc_admin_fetches_templatelist() throws Exception { + /* prepare */ + when(templateService.fetchAllTemplateIds()).thenReturn(List.of(TEST_TEMPLATE_ID1, TEST_TEMPLATE_ID2)); + + String apiEndpoint = https(PORT_USED).buildAdminFetchesTemplateList(); + Class useCase = UseCaseAdminFetchesAllTemplateIds.class; + + /* execute + test @formatter:off */ + this.mockMvc.perform( + get(apiEndpoint, TEST_TEMPLATE_ID1). + contentType(MediaType.APPLICATION_JSON_VALUE). + header(AuthenticationHelper.HEADER_NAME, AuthenticationHelper.getHeaderValue()) + ). + andExpect(status().isOk()). + andDo(defineRestService(). + with(). + useCaseData(useCase). + tag(RestDocFactory.extractTag(apiEndpoint)). + requestSchema(OpenApiSchema.TEMPLATES.getSchema()). + and(). + document( + responseFields( + fieldWithPath("[]").description("Array contains all existing template identifiers") + ) + )); + + /* @formatter:on */ + } +} diff --git a/sechub-integrationtest/pds/product-scripts/integrationtest-webscan.sh b/sechub-integrationtest/pds/product-scripts/integrationtest-webscan.sh index e049d6a539..c89235bf74 100755 --- a/sechub-integrationtest/pds/product-scripts/integrationtest-webscan.sh +++ b/sechub-integrationtest/pds/product-scripts/integrationtest-webscan.sh @@ -16,6 +16,15 @@ info:PDS_SCAN_CONFIGURATION=$PDS_SCAN_CONFIGURATION dumpPDSVariables + +# We added pds.config.templates.metadata.list as optional parameter here for testing +# So we can dump the variable here - used in scenario12 integration test +dumpVariable "PDS_CONFIG_TEMPLATE_METADATA_LIST" + +ASSET_FILE1="$PDS_JOB_EXTRACTED_ASSETS_FOLDER/webscan-login/testfile1.txt" +TEST_CONTENT_FROM_ASSETFILE=$(cat $ASSET_FILE1) +# Afterwards TEST_CONTENT_FROM_ASSETFILE=i am "testfile1.txt" for scenario12 integration tests +dumpVariable "TEST_CONTENT_FROM_ASSETFILE" if [[ "$PDS_TEST_KEY_VARIANTNAME" = "a" ]]; then diff --git a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/AsUser.java b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/AsUser.java index 1e26c38bf5..dd5d0c08cd 100644 --- a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/AsUser.java +++ b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/AsUser.java @@ -24,7 +24,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; +import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RequestCallback; import org.springframework.web.client.ResponseExtractor; import org.springframework.web.client.RestClientException; @@ -37,6 +40,9 @@ import com.mercedesbenz.sechub.commons.model.JSONConverter; import com.mercedesbenz.sechub.commons.model.SecHubConfigurationModel; import com.mercedesbenz.sechub.commons.model.TrafficLight; +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition; +import com.mercedesbenz.sechub.domain.administration.project.ProjectDetailInformation; +import com.mercedesbenz.sechub.domain.scan.asset.AssetDetailData; import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectData; import com.mercedesbenz.sechub.integrationtest.JSONTestSupport; import com.mercedesbenz.sechub.integrationtest.internal.IntegrationTestContext; @@ -1425,4 +1431,111 @@ public SecHubEncryptionStatus fetchEncryptionStatus() { return SecHubEncryptionStatus.fromString(json); } + public AsUser createOrUpdateTemplate(String templateId, TemplateDefinition definition) { + String url = getUrlBuilder().buildAdminCreatesOrUpdatesTemplate(templateId); + getRestHelper().putJSON(url, definition.toFormattedJSON()); + return this; + } + + public TemplateDefinition fetchTemplateDefinitionOrNull(String templateId) { + String url = getUrlBuilder().buildAdminFetchesTemplate(templateId); + try { + String json = getRestHelper().getJSON(url); + return TemplateDefinition.from(json); + + } catch (HttpClientErrorException e) { + HttpStatusCode statusCode = e.getStatusCode(); + if (statusCode.equals(HttpStatus.NOT_FOUND)) { + return null; + } + throw e; + } + } + + public void assignTemplateToProject(String templateid, TestProject project) { + String url = getUrlBuilder().buildAdminAssignsTemplateToProjectUrl(templateid, project.getProjectId()); + getRestHelper().put(url); + } + + public void unassignTemplateFromProject(String templateid, TestProject project) { + String url = getUrlBuilder().buildAdminUnAssignsTemplateToProjectUrl(templateid, project.getProjectId()); + getRestHelper().delete(url); + } + + public ProjectDetailInformation fetchProjectDetailInformation(TestProject project) { + String url = getUrlBuilder().buildAdminFetchProjectInfoUrl(project.getProjectId()); + String json = getRestHelper().getJSON(url); + ProjectDetailInformation result = JSONConverter.get().fromJSON(ProjectDetailInformation.class, json); + return result; + } + + public void deleteTemplate(String templateId) { + String url = getUrlBuilder().buildAdminDeletesTemplate(templateId); + getRestHelper().delete(url); + } + + public List fetchTemplateList() { + String url = getUrlBuilder().buildAdminFetchesTemplateList(); + String json = getRestHelper().getJSON(url); + return JSONConverter.get().fromJSONtoListOf(String.class, json); + } + + public AsUser uploadAssetFile(String assetId, File file) { + String url = getUrlBuilder().buildAdminUploadsAssetFile(assetId); + String checkSum = TestAPI.createSHA256Of(file); + /* @formatter:off */ + autoDumper.execute(() -> getRestHelper().upload(url,file,checkSum) + ); + /* @formatter:on */ + return this; + } + + public AsUser uploadAssetFiles(String assetId, File... files) { + for (File file : files) { + uploadAssetFile(assetId, file); + } + return this; + } + + public File downloadAssetFile(String assetId, String fileName) { + String url = getUrlBuilder().buildAdminDownloadsAssetFile(assetId, fileName); + /* @formatter:off */ + RequestCallback requestCallback = request -> request.getHeaders().setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL)); + + ResponseExtractor responseExtractor = response -> { + Path path = TestUtil.createTempFileInBuildSubFolder("assets/"+assetId, fileName); + Files.copy(response.getBody(), path, StandardCopyOption.REPLACE_EXISTING); + if (TestUtil.isDeletingTempFiles()) { + path.toFile().deleteOnExit(); + } + return path.toFile(); + }; + RestTemplate template = getRestHelper().getTemplate(); + File downloadedAssetFile = template.execute(url, HttpMethod.GET, requestCallback, responseExtractor); + + return downloadedAssetFile; + } + + public List fetchAllAssetIds() { + String url = getUrlBuilder().buildAdminFetchesAllAssetIds(); + String json = getRestHelper().getJSON(url); + return JSONConverter.get().fromJSONtoListOf(String.class, json); + } + + public AssetDetailData fetchAssetDetails(String assetId) { + String url = getUrlBuilder().buildAdminFetchesAssetDetails(assetId); + String json = getRestHelper().getJSON(url); + return JSONConverter.get().fromJSON(AssetDetailData.class, json); + } + + public void deleteAssetFile(String assetId, String fileName) { + String url = getUrlBuilder().buildAdminDeletesAssetFile(assetId, fileName); + getRestHelper().delete(url); + } + + public void deleteAsset(String assetId) { + String url = getUrlBuilder().buildAdminDeletesAsset(assetId); + getRestHelper().delete(url); + } + } diff --git a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/TestAPI.java b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/TestAPI.java index f83e330099..08289ece95 100644 --- a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/TestAPI.java +++ b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/TestAPI.java @@ -42,6 +42,7 @@ import com.mercedesbenz.sechub.commons.model.SecHubMessagesList; import com.mercedesbenz.sechub.commons.pds.data.PDSJobStatusState; import com.mercedesbenz.sechub.domain.scan.admin.FullScanData; +import com.mercedesbenz.sechub.domain.scan.project.ScanProjectConfig; import com.mercedesbenz.sechub.integrationtest.internal.DefaultTestExecutionProfile; import com.mercedesbenz.sechub.integrationtest.internal.IntegrationTestContext; import com.mercedesbenz.sechub.integrationtest.internal.IntegrationTestDefaultProfiles; @@ -588,6 +589,17 @@ public static void executeUntilSuccessOrTimeout(TestExecutable testExecutable) { return; } + /** + * Tries to execute runnable with default maximum time and retry (4 times a 500 + * milliseconds) Shortcut for + * executeRunnableAndAcceptAssertionsMaximumTimes(4,runnable, 500); + * + * @param runnable + */ + public static void executeResilient(Runnable runnable) { + executeRunnableAndAcceptAssertionsMaximumTimes(4, runnable, 500); + } + public static void executeRunnableAndAcceptAssertionsMaximumTimes(int tries, Runnable runnable, int millisBeforeNextRetry) { executeCallableAndAcceptAssertionsMaximumTimes(tries, () -> { runnable.run(); @@ -1694,4 +1706,10 @@ public static boolean isSecHubTerminating() { return getSuperAdminRestHelper().getBooleanFromURL(url); } + public static List fetchScanProjectConfigurations(TestProject project) { + String url = getURLBuilder().buildIntegrationTestFetchScanProjectConfigurations(project.getProjectId()); + String json = getSuperAdminRestHelper().getJSON(url); + return JSONConverter.get().fromJSONtoListOf(ScanProjectConfig.class, json); + + } } diff --git a/sechub-integrationtest/src/main/resources/pds-config-integrationtest.json b/sechub-integrationtest/src/main/resources/pds-config-integrationtest.json index 9b4651eaf0..79b6966008 100644 --- a/sechub-integrationtest/src/main/resources/pds-config-integrationtest.json +++ b/sechub-integrationtest/src/main/resources/pds-config-integrationtest.json @@ -5,7 +5,7 @@ "id" : "PDS_INTTEST_PRODUCT_CODESCAN", "path" : "./../sechub-integrationtest/pds/product-scripts/integrationtest-codescan.sh", "scanType" : "codeScan", - "envWhitelist" : [ "INTEGRATIONTEST_SCRIPT_ENV_ACCEPTED"], + "envWhitelist" : [ "INTEGRATIONTEST_SCRIPT_ENV_ACCEPTED" ], "description" : "This is only a fake code scan - used by integration tests. The code scan will just return data from uploaded zip file", "parameters" : { "mandatory" : [ { @@ -71,6 +71,9 @@ "optional" : [ { "key" : "pds.test.key.variantname", "description" : "a parameter from configuration - will be different in each integration test config from sechub integration test server" + }, { + "key" : "pds.config.template.metadata.list", + "description" : "normally this parameter is NOT sent to script, but for testing we add this parameter, so we can check by TestAPI..." } ] } }, { @@ -209,7 +212,7 @@ "path" : "./../sechub-integrationtest/pds/product-scripts/integrationtest-prepare.sh", "scanType" : "prepare", "description" : "This is a fake prepare scan - used by integration tests.", - "envWhitelist" : [ "PDS_STORAGE_*"], + "envWhitelist" : [ "PDS_STORAGE_*" ], "parameters" : { "optional" : [ { "key" : "pds.test.key.variantname", diff --git a/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario1/AssetScenario1IntTest.java b/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario1/AssetScenario1IntTest.java new file mode 100644 index 0000000000..f7417b9579 --- /dev/null +++ b/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario1/AssetScenario1IntTest.java @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.integrationtest.scenario1; + +import static com.mercedesbenz.sechub.integrationtest.api.TestAPI.*; +import static com.mercedesbenz.sechub.integrationtest.api.TestAPI.as; +import static org.assertj.core.api.Assertions.*; + +import java.io.File; +import java.util.List; +import java.util.UUID; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.Timeout; +import org.springframework.web.client.HttpClientErrorException.NotFound; + +import com.mercedesbenz.sechub.domain.scan.asset.AssetDetailData; +import com.mercedesbenz.sechub.domain.scan.asset.AssetFileData; +import com.mercedesbenz.sechub.integrationtest.api.IntegrationTestSetup; +import com.mercedesbenz.sechub.integrationtest.api.TestAPI; +import com.mercedesbenz.sechub.test.TestFileReader; + +public class AssetScenario1IntTest { + + @Rule + public IntegrationTestSetup setup = IntegrationTestSetup.forScenario(Scenario1.class); + + @Rule + public Timeout timeOut = Timeout.seconds(600); + + @Before + public void before() { + + } + + @Test + public void asset_crud_operation_working_as_expected() { + /* ------- */ + /* prepare */ + /* ------- */ + File uploadedFile1 = new File("./src/test/resources/asset/examples-1/asset1.txt"); + File uploadedFile2 = new File("./src/test/resources/asset/examples-1/asset2.txt"); + + String assetId = "crud" + UUID.randomUUID().toString(); + + /* --------------- */ + /* execute + test */ + /* --------------- */ + as(SUPER_ADMIN).uploadAssetFiles(assetId, uploadedFile1, uploadedFile2); + + // fetch all asset ids + List allAssetIds = as(SUPER_ADMIN).fetchAllAssetIds(); + assertThat(allAssetIds).contains(assetId); + + /* download files */ + File downloadedAssetFile1 = as(SUPER_ADMIN).downloadAssetFile(assetId, uploadedFile1.getName()); + + String output = TestFileReader.readTextFromFile(downloadedAssetFile1); + assertThat(output).isEqualTo("I am text file \"asset1.txt\""); + + /* fetch asset details and check content is as expected */ + AssetDetailData detailData = as(SUPER_ADMIN).fetchAssetDetails(assetId); + assertThat(detailData.getAssetId()).isEqualTo(assetId); + + String checksum1 = TestAPI.createSHA256Of(uploadedFile1); + AssetFileData expectedInfo1 = new AssetFileData(); + expectedInfo1.setChecksum(checksum1); + expectedInfo1.setFileName("asset1.txt"); + + String checksum2 = TestAPI.createSHA256Of(uploadedFile2); + AssetFileData expectedInfo2 = new AssetFileData(); + expectedInfo2.setChecksum(checksum2); + expectedInfo2.setFileName("asset2.txt"); + + assertThat(detailData.getFiles()).contains(expectedInfo1, expectedInfo2).hasSize(2); + + /* delete single file from asset */ + as(SUPER_ADMIN).deleteAssetFile(assetId, "asset1.txt"); + + /* check asset still exists in list and details contain only asset2.txt */ + assertThat(as(SUPER_ADMIN).fetchAllAssetIds()).contains(assetId); + assertThat(as(SUPER_ADMIN).fetchAssetDetails(assetId).getFiles()).containsOnly(expectedInfo2); + + /* + * Upload asset 2 again, but with different content - we use other file from + * examples-2 instead of examples-1. Will override existing asset file. + */ + File uploadedFile2changed = new File("./src/test/resources/asset/examples-2/asset2.txt"); + String checksum2changed = TestAPI.createSHA256Of(uploadedFile2changed); + assertThat(checksum2changed).as("precondition-check that files are different").isNotEqualTo(checksum2); + + as(SUPER_ADMIN).uploadAssetFile(assetId, uploadedFile2changed); + + AssetFileData expectedInfo2Canged = new AssetFileData(); + expectedInfo2Canged.setChecksum(checksum2changed); + expectedInfo2Canged.setFileName("asset2.txt"); + + assertThat(as(SUPER_ADMIN).fetchAssetDetails(assetId).getFiles()).containsOnly(expectedInfo2Canged); + + output = TestFileReader.readTextFromFile(as(SUPER_ADMIN).downloadAssetFile(assetId, "asset2.txt")); + assertThat(output).isEqualTo("I am text file \"asset2.txt\" - but from folder example-2"); + + /* delete complete asset */ + as(SUPER_ADMIN).deleteAsset(assetId); + + assertThat(as(SUPER_ADMIN).fetchAllAssetIds()).doesNotContain(assetId); + assertThatThrownBy(() -> as(SUPER_ADMIN).fetchAssetDetails(assetId)).isInstanceOf(NotFound.class); + } + +} diff --git a/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario1/TemplateScenario1IntTest.java b/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario1/TemplateScenario1IntTest.java new file mode 100644 index 0000000000..cc97b09d0c --- /dev/null +++ b/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario1/TemplateScenario1IntTest.java @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.integrationtest.scenario1; + +import static com.mercedesbenz.sechub.integrationtest.api.TestAPI.*; +import static com.mercedesbenz.sechub.integrationtest.api.TestAPI.as; +import static org.assertj.core.api.Assertions.*; + +import java.util.List; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.Timeout; + +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition; +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition.TemplateVariable; +import com.mercedesbenz.sechub.commons.model.template.TemplateType; +import com.mercedesbenz.sechub.domain.scan.project.ScanProjectConfig; +import com.mercedesbenz.sechub.domain.scan.project.ScanProjectConfigID; +import com.mercedesbenz.sechub.integrationtest.api.IntegrationTestSetup; + +public class TemplateScenario1IntTest { + + @Rule + public IntegrationTestSetup setup = IntegrationTestSetup.forScenario(Scenario1.class); + + @Rule + public Timeout timeOut = Timeout.seconds(600); + + private String templateId; + + private TemplateDefinition createDefinition; + + private TemplateDefinition definitionWithId; + private TemplateDefinition updateDefinition; + + @Before + public void before() { + + templateId = "template-1_" + System.nanoTime(); + + /* @formatter:off */ + TemplateDefinition fullTemplateDefinition = TemplateDefinition.builder(). + templateId(templateId). + templateType(TemplateType.WEBSCAN_LOGIN). + assetId("asset1"). + build(); + /* @formatter:on */ + TemplateVariable usernameVariable = new TemplateVariable(); + usernameVariable.setName("username"); + + TemplateVariable passwordVariable = new TemplateVariable(); + passwordVariable.setName("password"); + + fullTemplateDefinition.getVariables().add(usernameVariable); + fullTemplateDefinition.getVariables().add(passwordVariable); + + String fullTemplateDefinitionJson = fullTemplateDefinition.toFormattedJSON(); + createDefinition = TemplateDefinition.from(fullTemplateDefinitionJson.replace(templateId, "does-not-matter-will-be-overriden")); + + definitionWithId = TemplateDefinition.from(fullTemplateDefinitionJson); + + updateDefinition = TemplateDefinition.from(fullTemplateDefinitionJson.replace(templateId, "will-not-be-changed-by-update")); + } + + @Test + public void template_crud_test() { + /* prepare */ + as(SUPER_ADMIN).createProject(Scenario1.PROJECT_1, SUPER_ADMIN); // not done in this scenario automatically + + /* check preconditions */ + assertTemplateNotInsideTemplateList(); + + /* execute + test */ + assertTemplateCanBeCreated(); + + assertTemplateCanBeUpdated(); + + assertTemplateCanBeAssignedToProject(); + + assertTemplateCanBeUnassignedFromProject(); + + assertTemplateCanBeAssignedToProject(); + + assertTemplateCanBeDeletedAndAssignmentIsPurged(); + + assertTemplateCanBeRecreatedWithSameId(); + + assertTemplateCanBeAssignedToProject(); + + assertProjectDeleteDoesPurgeTemplateAssignment(); + + assertTemplateExistsInTemplateListAndCanBeFetched(); + + /* + * cleanup - we remove the re-created template finally to have no garbage after + * test + */ + as(SUPER_ADMIN).deleteTemplate(templateId); + + // check cleanup worked + assertTemplateNotInsideTemplateList(); + + } + + private void assertTemplateNotInsideTemplateList() { + List templateIds = as(SUPER_ADMIN).fetchTemplateList(); + executeResilient(() -> assertThat(templateIds).doesNotContain(templateId)); + } + + private void assertTemplateExistsInTemplateListAndCanBeFetched() { + // check template list still contains the test template */ + executeResilient(() -> assertThat(as(SUPER_ADMIN).fetchTemplateList()).contains(templateId)); + executeResilient(() -> assertThat(as(SUPER_ADMIN).fetchTemplateDefinitionOrNull(templateId)).isNotNull()); + } + + private void assertProjectDeleteDoesPurgeTemplateAssignment() { + /* execute 7 - delete project */ + as(SUPER_ADMIN).deleteProject(Scenario1.PROJECT_1); + + /* test 7 - configuration for project is removed */ + executeResilient(() -> assertThat(fetchScanProjectConfigurations(Scenario1.PROJECT_1)).isEmpty()); + + } + + private void assertTemplateCanBeRecreatedWithSameId() { + /* execute 6 - create template with same id again */ + as(SUPER_ADMIN).createOrUpdateTemplate(templateId, createDefinition); + + /* test 6 - template is recreated */ + executeResilient(() -> assertThat(as(SUPER_ADMIN).fetchTemplateDefinitionOrNull(templateId)).isNotNull()); + } + + private void assertTemplateCanBeDeletedAndAssignmentIsPurged() { + /* execute 5 - delete template */ + as(SUPER_ADMIN).deleteTemplate(templateId); + + /* test 5.1 check delete unassigns template */ + executeResilient(() -> assertThat(as(SUPER_ADMIN).fetchProjectDetailInformation(Scenario1.PROJECT_1).getTemplateIds()).contains(templateId)); + + /* test 5.2 check template no longer exists */ + executeResilient(() -> assertThat(as(SUPER_ADMIN).fetchTemplateDefinitionOrNull(templateId)).isNull()); + } + + private void assertTemplateCanBeUnassignedFromProject() { + /* execute 4 - unassign */ + as(SUPER_ADMIN).unassignTemplateFromProject(templateId, Scenario1.PROJECT_1); + + /* test 4 - check assignment */ + executeResilient(() -> assertThat(as(SUPER_ADMIN).fetchProjectDetailInformation(Scenario1.PROJECT_1).getTemplateIds()).isEmpty()); + executeResilient(() -> assertThat(fetchScanProjectConfigurations(Scenario1.PROJECT_1)).isEmpty()); + } + + private void assertTemplateCanBeAssignedToProject() { + + /* execute 3- assign */ + as(SUPER_ADMIN).assignTemplateToProject(templateId, Scenario1.PROJECT_1); + + /* test 3.1 - check assignment by project details in domain administration */ + executeResilient(() -> assertThat(as(SUPER_ADMIN).fetchProjectDetailInformation(Scenario1.PROJECT_1).getTemplateIds()).contains(templateId)); + + /* test 3.2 - check project scan configuration in domain scan */ + executeResilient(() -> { + List configurations = fetchScanProjectConfigurations(Scenario1.PROJECT_1); + assertThat(configurations).isNotEmpty().hasSize(1) + .contains(new ScanProjectConfig(ScanProjectConfigID.TEMPLATE_WEBSCAN_LOGIN, Scenario1.PROJECT_1.getProjectId())); + assertThat(configurations.iterator().next().getData()).isEqualTo(templateId); + }); + } + + private void assertTemplateCanBeUpdated() { + /* prepare 2 - update */ + updateDefinition.setAssetId("asset2"); + + /* execute 2 - update */ + as(SUPER_ADMIN).createOrUpdateTemplate(templateId, updateDefinition); + + /* test 2 - update works */ + executeResilient(() -> { + TemplateDefinition loadedTemplate = as(SUPER_ADMIN).fetchTemplateDefinitionOrNull(templateId); + assertThat(loadedTemplate.getAssetId()).isEqualTo("asset2"); + assertThat(loadedTemplate.getType()).isEqualTo(TemplateType.WEBSCAN_LOGIN); + assertThat(loadedTemplate.getId()).isEqualTo(templateId); + }); + } + + private void assertTemplateCanBeCreated() { + /* execute 1 - create */ + as(SUPER_ADMIN).createOrUpdateTemplate(templateId, createDefinition); + + /* test 1 - created definition has content as expected and contains id */ + executeResilient( + () -> assertThat(as(SUPER_ADMIN).fetchTemplateDefinitionOrNull(templateId).toFormattedJSON()).isEqualTo(definitionWithId.toFormattedJSON())); + } + +} diff --git a/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario12/PDSWebScanJobScenario12IntTest.java b/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario12/PDSWebScanJobScenario12IntTest.java index 67a8317c98..f813483f04 100644 --- a/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario12/PDSWebScanJobScenario12IntTest.java +++ b/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario12/PDSWebScanJobScenario12IntTest.java @@ -2,12 +2,16 @@ package com.mercedesbenz.sechub.integrationtest.scenario12; import static com.mercedesbenz.sechub.integrationtest.api.TestAPI.*; +import static com.mercedesbenz.sechub.integrationtest.api.TestAPI.as; import static com.mercedesbenz.sechub.integrationtest.scenario12.Scenario12.*; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.*; +import java.io.File; import java.util.Arrays; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.UUID; @@ -15,7 +19,18 @@ import org.junit.Test; import org.junit.rules.Timeout; -import com.mercedesbenz.sechub.commons.model.*; +import com.mercedesbenz.sechub.commons.model.ClientCertificateConfiguration; +import com.mercedesbenz.sechub.commons.model.HTTPHeaderConfiguration; +import com.mercedesbenz.sechub.commons.model.JSONConverter; +import com.mercedesbenz.sechub.commons.model.SecHubMessageType; +import com.mercedesbenz.sechub.commons.model.SecHubScanConfiguration; +import com.mercedesbenz.sechub.commons.model.SecHubWebScanApiConfiguration; +import com.mercedesbenz.sechub.commons.model.SecHubWebScanApiType; +import com.mercedesbenz.sechub.commons.model.SecHubWebScanConfiguration; +import com.mercedesbenz.sechub.commons.model.Severity; +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition; +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition.TemplateVariable; +import com.mercedesbenz.sechub.commons.model.template.TemplateType; import com.mercedesbenz.sechub.integrationtest.api.IntegrationTestSetup; import com.mercedesbenz.sechub.integrationtest.api.TestProject; import com.mercedesbenz.sechub.integrationtest.internal.IntegrationTestFileSupport; @@ -35,18 +50,68 @@ public class PDSWebScanJobScenario12IntTest { @Rule public Timeout timeOut = Timeout.seconds(600); + /* @formatter:off + * + * This is test is a web scan integration test which + * tests multiple features. + * + * The test prepares + * - web scan in general with dedicated setup + * - uses a SecHub configuration with template data inside + * - creates an asset, creates a template which uses the asset, assigns template + * + * The tests checks following: + * + * - PDS web scan has expected info finding, with + * - given target URL + * - product level information + * - SecHub web configuration parts + * + * - PDS parameter for template meta data configuration is correct and transmitted to PDS + * The parameter "pds.config.template.metadata.list" is normally not available inside + * the scripts, but for testing we added the parameter inside server configuration so it + * will be added to script level and can be checked by TestAPI + * + * - PDS will download and extract the uploaded asset file automatically and the + * extracted content is available inside the test bash script (executed by PDS) + * + * + * @formatter:on + */ @Test - public void pds_web_scan_has_expected_info_finding_with_given_target_url_and_product2_level_information_and_sechub_web_config_parts() { + public void pds_web_scan_can_be_executed_and_works() throws Exception { /* @formatter:off */ /* prepare */ + String assetId="asset-s12-pds-inttest-webscan"; + File productZipFile = IntegrationTestFileSupport.getTestfileSupport().createFileFromResourcePath("/asset/scenario12/PDS_INTTEST_PRODUCT_WEBSCAN.zip"); + String configurationAsJson = IntegrationTestFileSupport.getTestfileSupport().loadTestFile("sechub-integrationtest-webscanconfig-all-options.json"); SecHubScanConfiguration configuration = SecHubScanConfiguration.createFromJSON(configurationAsJson); configuration.setProjectId("myTestProject"); TestProject project = PROJECT_1; String targetURL = configuration.getWebScan().get().getUrl().toString(); - as(SUPER_ADMIN).updateWhiteListForProject(project, Arrays.asList(targetURL)); + + TemplateVariable userNameVariable = new TemplateVariable(); + userNameVariable.setName("username"); + + TemplateVariable passwordVariable = new TemplateVariable(); + passwordVariable.setName("password"); + + TemplateDefinition templateDefinition = new TemplateDefinition(); + templateDefinition.setAssetId(assetId); + templateDefinition.setType(TemplateType.WEBSCAN_LOGIN); + templateDefinition.getVariables().add(userNameVariable); + templateDefinition.getVariables().add(passwordVariable); + + String templateId = "template-scenario12-1"; + as(SUPER_ADMIN). + updateWhiteListForProject(project, Arrays.asList(targetURL)). + uploadAssetFile(assetId, productZipFile). + createOrUpdateTemplate(templateId, templateDefinition). + assignTemplateToProject(templateId, project) + ; /* execute */ UUID jobUUID = as(USER_1).withSecHubClient().startAsynchronScanFor(project, configuration).getJobUUID(); @@ -101,13 +166,13 @@ public void pds_web_scan_has_expected_info_finding_with_given_target_url_and_pro assertTrue(includes.contains("/customer/<*>")); assertTrue(excludes.contains("<*>/admin/<*>")); - // config must contain the expected headers + // web configuration must contain the expected headers assertExpectedHeaders(webConfiguration); - // config must contain the expected client certificate + // web configuration must contain the expected client certificate assertExpectedClientCertificate(webConfiguration); - // config must contain the expected openApi definition + // web configuration must contain the expected openApi definition assertExpectedOpenApiDefinition(webConfiguration); /* additional testing : messages*/ @@ -119,6 +184,14 @@ public void pds_web_scan_has_expected_info_finding_with_given_target_url_and_pro hasMessage(SecHubMessageType.ERROR,"error from webscan by PDS for sechub job uuid: "+jobUUID). hasMessage(SecHubMessageType.INFO, "another-token.txtbearer-token.txtcertificate.p12openapi.json"); + UUID pdsJobUUID = waitForFirstPDSJobOfSecHubJobAndReturnPDSJobUUID(jobUUID); + Map variables = fetchPDSVariableTestOutputMap(pdsJobUUID); + + String expectedMetaDataListJson = """ + [{"templateId":"template-scenario12-1","templateType":"WEBSCAN_LOGIN","assetData":{"assetId":"asset-s12-pds-inttest-webscan","fileName":"PDS_INTTEST_PRODUCT_WEBSCAN.zip","checksum":"ff06430bfc2d8c698ab8effa41b914525b8cca1c1eecefa76d248b25cc598fba"}}] + """.trim(); + assertThat(variables.get("PDS_CONFIG_TEMPLATE_METADATA_LIST")).isEqualTo(expectedMetaDataListJson); + assertThat(variables.get("TEST_CONTENT_FROM_ASSETFILE")).isEqualTo("i am \"testfile1.txt\" for scenario12 integration tests"); /* @formatter:on */ } diff --git a/sechub-integrationtest/src/test/resources/asset/examples-1/asset1.txt b/sechub-integrationtest/src/test/resources/asset/examples-1/asset1.txt new file mode 100644 index 0000000000..c9f4187cc1 --- /dev/null +++ b/sechub-integrationtest/src/test/resources/asset/examples-1/asset1.txt @@ -0,0 +1 @@ +I am text file "asset1.txt" \ No newline at end of file diff --git a/sechub-integrationtest/src/test/resources/asset/examples-1/asset2.txt b/sechub-integrationtest/src/test/resources/asset/examples-1/asset2.txt new file mode 100644 index 0000000000..a47cb9bda8 --- /dev/null +++ b/sechub-integrationtest/src/test/resources/asset/examples-1/asset2.txt @@ -0,0 +1 @@ +I am text file "asset2.txt" \ No newline at end of file diff --git a/sechub-integrationtest/src/test/resources/asset/examples-2/asset2.txt b/sechub-integrationtest/src/test/resources/asset/examples-2/asset2.txt new file mode 100644 index 0000000000..7ccc0dabcc --- /dev/null +++ b/sechub-integrationtest/src/test/resources/asset/examples-2/asset2.txt @@ -0,0 +1 @@ +I am text file "asset2.txt" - but from folder example-2 \ No newline at end of file diff --git a/sechub-integrationtest/src/test/resources/asset/scenario12/PDS_INTTEST_PRODUCT_WEBSCAN.zip b/sechub-integrationtest/src/test/resources/asset/scenario12/PDS_INTTEST_PRODUCT_WEBSCAN.zip new file mode 100644 index 0000000000..1c1ce661bf Binary files /dev/null and b/sechub-integrationtest/src/test/resources/asset/scenario12/PDS_INTTEST_PRODUCT_WEBSCAN.zip differ diff --git a/sechub-integrationtest/src/test/resources/sechub-integrationtest-webscanconfig-all-options.json b/sechub-integrationtest/src/test/resources/sechub-integrationtest-webscanconfig-all-options.json index 6951487d61..894924e54f 100644 --- a/sechub-integrationtest/src/test/resources/sechub-integrationtest-webscanconfig-all-options.json +++ b/sechub-integrationtest/src/test/resources/sechub-integrationtest-webscanconfig-all-options.json @@ -25,8 +25,8 @@ }, "webScan" : { "url" : "https://demo.example.org/myapp", - "includes": [ "/portal/admin", "/abc.html", "/hidden", "/customer/<*>" ], - "excludes": [ "/public/media", "/contact.html", "/static", "<*>/admin/<*>" ], + "includes" : [ "/portal/admin", "/abc.html", "/hidden", "/customer/<*>" ], + "excludes" : [ "/public/media", "/contact.html", "/static", "<*>/admin/<*>" ], "maxScanDuration" : { "duration" : 35, "unit" : "minutes" @@ -39,7 +39,7 @@ "password" : "secret-password", "use" : [ "client-cert-api-file-reference" ] }, - "headers" : [{ + "headers" : [ { "name" : "Authorization", "use" : [ "header-file-ref-for-big-token" ] }, { @@ -50,9 +50,13 @@ }, { "name" : "Key", "use" : [ "another-header-file-ref-for-big-token" ] - }], + } ], "login" : { "url" : "https://demo.example.org/myapp/login", + "templateData" : { + "username" : "testuser", + "password" : "testpwd" + }, "basic" : { "realm" : "realm0", "user" : "user0", diff --git a/sechub-openapi-java/src/main/resources/openapi.yaml b/sechub-openapi-java/src/main/resources/openapi.yaml index 441b015945..89428aa834 100644 --- a/sechub-openapi-java/src/main/resources/openapi.yaml +++ b/sechub-openapi-java/src/main/resources/openapi.yaml @@ -150,6 +150,11 @@ components: items: type: string type: array + templates: + description: A list of all assigned templates + items: + type: string + type: array example: owner: owner metaData: @@ -163,6 +168,9 @@ components: users: - user1 - user2 + templates: + - template1 + - template2 ProjectWhitelist: title: ProjectWhitelist @@ -2017,16 +2025,81 @@ components: $ref: '#/components/schemas/SecHubJobInfoForUser' projectId: type: string - - + + TemplateType: + title: TemplateType + type: string + enum: + - WEBSCAN_LOGIN + + TemplateVariableValidation: + title: TemplateVariableValidation + type: object + properties: + minLength: + type: integer + format: int32 + maxLength: + type: integer + format: int32 + regularExpression: + type: string + + TemplateVariable: + title: TemplateVariable + type: object + properties: + name: + type: string + optional: + type: boolean + validation: + $ref: '#/components/schemas/TemplateVariableValidation' + + TemplateDefinition: + title: TemplateDefinition + type: object + properties: + type: + $ref: '#/components/schemas/TemplateType' + assets: + type: array + items: + type: string + variables: + type: array + items: + $ref: '#/components/schemas/TemplateVariable' + + AssetDetailData: + title: AssetDetailData + type: object + properties: + assetId: + type: string + files: + type: array + items: + type: object + $ref: '#/components/schemas/AssetFileData' + + AssetFileData: + title: AssetFileData + type: object + properties: + fileName: + type: string + checksum: + type: string + security: - basicAuth: [ ] paths: - ############# + ############ ## System ## - ############# + ############ /api/anonymous/check/alive: get: @@ -2547,7 +2620,60 @@ paths: description: "Not acceptable" tags: - Project Administration - + /api/admin/project/{projectId}/template/{templateId}: + put: + summary: Admin assigns template to project + description: An administrator assigns a template to a project + operationId: adminAssignTemplateToProject + parameters: + - name: projectId + description: The id for project + in: path + required: true + schema: + type: string + - name: templateId + description: The id of the template to assign to project + in: path + required: true + schema: + type: string + responses: + "200": + description: "Ok" + "404": + description: "Not found" + "406": + description: "Not acceptable" + tags: + - Project Administration + delete: + summary: Admin unassigns template from project + description: An administrator unassigns a template from a project + operationId: adminUnassignTemplateFromProject + parameters: + - name: projectId + description: The id for project + in: path + required: true + schema: + type: string + - name: templateId + description: The id of the template to assign to project + in: path + required: true + schema: + type: string + responses: + "200": + description: "Ok" + "404": + description: "Not found" + "406": + description: "Not acceptable" + tags: + - Project Administration + ################### ## User Profile ## ################### @@ -3789,4 +3915,256 @@ paths: description: "Not acceptable" x-content-type: application/json tags: - - Other \ No newline at end of file + - Other + + /api/admin/template/{templateId}: + put: + summary: Admin creates or updates a template + description: An administrator wants to create a new template or to update a template definition + operationId: adminCreateOrUpdateTemplate + parameters: + - name: templateId + description: The template id + in: path + required: true + schema: + type: string + responses: + "200": + description: "Ok" + "404": + description: "Not found" + "406": + description: "Not acceptable" + tags: + - Configuration + + delete: + summary: Admin deletes a template + description: An administrator wants to delete an existing template + operationId: adminDeleteTemplate + parameters: + - name: templateId + description: The template id + in: path + required: true + schema: + type: string + responses: + "200": + description: "Ok" + "404": + description: "Not found" + tags: + - Configuration + + get: + summary: Admin fetches template + description: An administrator wants to fetch the template definition by template id + operationId: adminFetchTemplate + parameters: + - name: templateId + description: The template id + in: path + required: true + schema: + type: string + responses: + "200": + description: "Ok" + content: + application/json: + schema: + $ref: '#/components/schemas/TemplateDefinition' + "404": + description: "Not found" + tags: + - Configuration + + /api/admin/templates: + get: + summary: Admin fetches template ids + description: An administrator wants to fetch a list containing all available template identifiers + operationId: adminFetchTemplateIds + parameters: + - name: templateId + description: The template id + in: path + required: true + schema: + type: string + responses: + "200": + description: "Ok" + content: + application/json;charset=UTF-8: + schema: + type: array + items: + type: string + "404": + description: "Not found" + tags: + - Configuration + + /api/admin/asset/ids: + get: + summary: Admin fetches asset ids + description: An administrator fetches all available asset ids. + operationId: adminFetchAssetIds + responses: + "200": + description: "Ok" + content: + application/json;charset=UTF-8: + schema: + type: array + items: + type: string + tags: + - Configuration + + /api/admin/asset/{assetId}/details: + get: + summary: Admin fetches asset details + description: "An administrator fetches details about an asset. For example: the result will contain names but also checksum of files." + operationId: adminFetchAssetDetails + parameters: + - name: assetId + description: The asset identifier + in: path + required: true + schema: + type: string + responses: + "200": + description: "Ok" + content: + application/json: + schema: + $ref: '#/components/schemas/AssetDetailData' + + "404": + description: "Not found" + tags: + - Configuration + + /api/admin/asset/{assetId}: + delete: + summary: Admin deletes asset comletely + description: An administrator deletes an asset completely. + operationId: adminDeletesAssetCompletely + parameters: + - name: assetId + description: TThe asset identifier for the asset which shall be deleted completely + in: path + required: true + schema: + type: string + responses: + "200": + description: "Ok" + + "404": + description: "Not found" + tags: + - Configuration + + /api/admin/asset/{assetId}/file: + post: + summary: Admin uploads an asset file + description: "An administrator uploads a file for an asset. If the file already exits, it will be overriden." + operationId: adminUploadsAssetFile + parameters: + - name: assetId + description: The id of the asset to which the uploaded file belongs to + in: path + required: true + schema: + type: string + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + file: + type: string + format: binary + description: The asset file to upload + checkSum: + type: string + description: A sha256 checksum for file upload validation + required: + - file + - checkSum + encoding: + file: + contentType: multipart/form-data + responses: + "200": + description: "Ok" + "406": + description: "Not acceptable" + x-content-type: multipart/form-data + tags: + - Configuration + + /api/admin/asset/{assetId}/file/{fileName}: + get: + summary: Admin downloads an asset file + description: An administrator downloads a file fom an asset. + operationId: adminDownloadsAssetFile + parameters: + - name: assetId + description: The asset identifier of the asset containing the file. + in: path + required: true + schema: + type: string + - name: fileName + description: The name of the file inside the asset which shall be downloaded. + in: path + required: true + schema: + type: string + responses: + "200": + content: + application/octet-stream: + schema: + type: string + format: binary + description: "Ok" + + "404": + description: "Not found" + tags: + - Configuration + + delete: + summary: Admin deletes an asset file + description: An administrator deletes a file fom an asset. + operationId: adminDeletesAssetFile + parameters: + - name: assetId + description: The asset identifier for the asset in which the file shall be deleted. + in: path + required: true + schema: + type: string + - name: fileName + description: The name of the file to delete inside the asset. + in: path + required: true + schema: + type: string + responses: + "200": + description: "Ok" + + "404": + description: "Not found" + tags: + - Configuration \ No newline at end of file diff --git a/sechub-pds-core/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJobConfigurationSupport.java b/sechub-pds-core/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJobConfigurationSupport.java index 8482876303..414831af46 100644 --- a/sechub-pds-core/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJobConfigurationSupport.java +++ b/sechub-pds-core/src/main/java/com/mercedesbenz/sechub/pds/job/PDSJobConfigurationSupport.java @@ -13,12 +13,14 @@ import org.slf4j.LoggerFactory; import com.mercedesbenz.sechub.commons.core.util.SimpleStringUtils; +import com.mercedesbenz.sechub.commons.model.JSONConverter; import com.mercedesbenz.sechub.commons.model.SecHubConfigurationModel; import com.mercedesbenz.sechub.commons.model.SecHubDataConfigurationType; import com.mercedesbenz.sechub.commons.model.SecHubDataConfigurationTypeListParser; import com.mercedesbenz.sechub.commons.model.SecHubScanConfiguration; import com.mercedesbenz.sechub.commons.pds.PDSDefaultParameterKeyConstants; import com.mercedesbenz.sechub.commons.pds.PDSDefaultParameterValueConstants; +import com.mercedesbenz.sechub.commons.pds.data.PDSTemplateMetaData; import com.mercedesbenz.sechub.pds.execution.PDSExecutionParameterEntry; public class PDSJobConfigurationSupport { @@ -200,4 +202,13 @@ public int getJobStorageReadResiliencRetryWaitSeconds(int defaultValue) { return result; } + public List getTemplateMetaData() { + + String json = getStringParameterOrNull(PARAM_KEY_PDS_CONFIG_TEMPLATE_META_DATA_LIST); + if (json == null || json.isBlank()) { + return Collections.emptyList(); + } + return JSONConverter.get().fromJSONtoListOf(PDSTemplateMetaData.class, json); + } + } diff --git a/sechub-pds-core/src/main/java/com/mercedesbenz/sechub/pds/usecase/PDSUseCaseIdentifier.java b/sechub-pds-core/src/main/java/com/mercedesbenz/sechub/pds/usecase/PDSUseCaseIdentifier.java index 70de74439b..f59264d4dc 100644 --- a/sechub-pds-core/src/main/java/com/mercedesbenz/sechub/pds/usecase/PDSUseCaseIdentifier.java +++ b/sechub-pds-core/src/main/java/com/mercedesbenz/sechub/pds/usecase/PDSUseCaseIdentifier.java @@ -47,6 +47,8 @@ public enum PDSUseCaseIdentifier { UC_SYSTEM_SIGTERM_HANDLING(19, false), + UC_SYSTEM_JOB_EXECUTION(20, false), + ; /* +---------------------------------------------------------------------+ */ diff --git a/sechub-pds-core/src/main/java/com/mercedesbenz/sechub/pds/usecase/UseCaseSystemExecutesJob.java b/sechub-pds-core/src/main/java/com/mercedesbenz/sechub/pds/usecase/UseCaseSystemExecutesJob.java new file mode 100644 index 0000000000..d3be4feb73 --- /dev/null +++ b/sechub-pds-core/src/main/java/com/mercedesbenz/sechub/pds/usecase/UseCaseSystemExecutesJob.java @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.pds.usecase; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/* @formatter:off */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@PDSUseCaseDefinition( + id=PDSUseCaseIdentifier.UC_SYSTEM_JOB_EXECUTION, + group=PDSUseCaseGroup.JOB_EXECUTION, + title="System executes job", + description="The PDS does execute a PDS job.") +public @interface UseCaseSystemExecutesJob { + PDSStep value(); +} diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/batch/PDSBatchTriggerService.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/batch/PDSBatchTriggerService.java index 5fa5765a36..6485696c34 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/batch/PDSBatchTriggerService.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/batch/PDSBatchTriggerService.java @@ -16,6 +16,8 @@ import com.mercedesbenz.sechub.pds.execution.PDSExecutionService; import com.mercedesbenz.sechub.pds.job.PDSJobRepository; import com.mercedesbenz.sechub.pds.job.PDSJobTransactionService; +import com.mercedesbenz.sechub.pds.usecase.PDSStep; +import com.mercedesbenz.sechub.pds.usecase.UseCaseSystemExecutesJob; import jakarta.annotation.PostConstruct; @@ -59,6 +61,7 @@ protected void postConstruct() { // default 10 seconds delay and 5 seconds initial @Scheduled(initialDelayString = "${pds.config.trigger.nextjob.initialdelay:" + DEFAULT_INITIAL_DELAY_MILLIS + "}", fixedDelayString = "${pds.config.trigger.nextjob.delay:" + DEFAULT_FIXED_DELAY_MILLIS + "}") + @UseCaseSystemExecutesJob(@PDSStep(number = 1, name = "PDS trigger service fills queue", description = "Trigger service adds jobs to queue (when queue not full)")) public void triggerExecutionOfNextJob() { if (!schedulingEnabled) { LOG.trace("Trigger execution of next job canceled, because scheduling disabled."); @@ -86,8 +89,7 @@ public void triggerExecutionOfNextJob() { } catch (ObjectOptimisticLockingFailureException e) { /* * This can happen when PDS instances are started at same time, so the check for - * next jobs can lead to race condiitons - and optmistic locks will occurre - * here. + * next jobs can lead to race conditions - and optimistic locks will occur here. * * To avoid this to happen again, we wait a random time here. So next call on * this machine should normally not collide again. diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionCallable.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionCallable.java index 6dfb1085f2..61bac36ec3 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionCallable.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionCallable.java @@ -39,6 +39,7 @@ import com.mercedesbenz.sechub.pds.usecase.UseCaseAdminFetchesJobErrorStream; import com.mercedesbenz.sechub.pds.usecase.UseCaseAdminFetchesJobMetaData; import com.mercedesbenz.sechub.pds.usecase.UseCaseAdminFetchesJobOutputStream; +import com.mercedesbenz.sechub.pds.usecase.UseCaseSystemExecutesJob; import com.mercedesbenz.sechub.pds.usecase.UseCaseSystemHandlesJobCancelRequests; import com.mercedesbenz.sechub.pds.util.PDSResilientRetryExecutor; import com.mercedesbenz.sechub.pds.util.PDSResilientRetryExecutor.ExceptionThrower; @@ -91,6 +92,7 @@ public void throwException(String message, Exception cause) throws IllegalStateE } @Override + @UseCaseSystemExecutesJob(@PDSStep(number = 3, name = "PDS execution call", description = "Central point of PDS job execution.")) public PDSExecutionResult call() throws Exception { LOG.info("Prepare execution of PDS job: {}", pdsJobUUID); PDSExecutionResult result = new PDSExecutionResult(); diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionEnvironmentService.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionEnvironmentService.java index 27fdcaa7eb..3896d68bcc 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionEnvironmentService.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionEnvironmentService.java @@ -105,6 +105,10 @@ private void addPdsJobRelatedVariables(UUID pdsJobUUID, Map map) map.put(PDS_JOB_SOURCECODE_ZIP_FILE, locationData.getSourceCodeZipFileLocation()); map.put(PDS_JOB_BINARIES_TAR_FILE, locationData.getBinariesTarFileLocation()); + String extractedAssetsLocation = locationData.getExtractedAssetsLocation(); + + map.put(PDS_JOB_EXTRACTED_ASSETS_FOLDER, extractedAssetsLocation); + String extractedSourcesLocation = locationData.getExtractedSourcesLocation(); map.put(PDS_JOB_SOURCECODE_UNZIPPED_FOLDER, extractedSourcesLocation); diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionService.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionService.java index 71131c1981..28f57eb91e 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionService.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/execution/PDSExecutionService.java @@ -37,6 +37,7 @@ import com.mercedesbenz.sechub.pds.job.PDSWorkspaceService; import com.mercedesbenz.sechub.pds.usecase.PDSStep; import com.mercedesbenz.sechub.pds.usecase.UseCaseAdminFetchesMonitoringStatus; +import com.mercedesbenz.sechub.pds.usecase.UseCaseSystemExecutesJob; import com.mercedesbenz.sechub.pds.usecase.UseCaseSystemHandlesJobCancelRequests; import com.mercedesbenz.sechub.pds.usecase.UseCaseSystemSigTermHandling; @@ -205,6 +206,7 @@ public boolean isQueueFull() { } @Async + @UseCaseSystemExecutesJob(@PDSStep(number = 2, name = "Add Job to queue", description = "PDS job is added to queue")) public void addToExecutionQueueAsynchron(UUID jobUUID) { Future former = null; synchronized (jobsInQueue) { diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSWorkspaceService.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSWorkspaceService.java index 329354db30..6fc14bbdfd 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSWorkspaceService.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/PDSWorkspaceService.java @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.pds.job; -import static com.mercedesbenz.sechub.commons.core.CommonConstants.FILENAME_BINARIES_TAR; -import static com.mercedesbenz.sechub.commons.core.CommonConstants.FILENAME_SOURCECODE_ZIP; +import static com.mercedesbenz.sechub.commons.core.CommonConstants.*; +import static java.util.Objects.*; import java.io.File; import java.io.FileFilter; @@ -15,6 +15,7 @@ import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; import java.time.Duration; +import java.util.List; import java.util.Set; import java.util.UUID; @@ -34,10 +35,14 @@ import com.mercedesbenz.sechub.commons.archive.ArchiveSupport.ArchiveType; import com.mercedesbenz.sechub.commons.archive.FileSize; import com.mercedesbenz.sechub.commons.archive.SecHubFileStructureDataProvider; +import com.mercedesbenz.sechub.commons.core.security.CheckSumSupport; import com.mercedesbenz.sechub.commons.model.JSONConverter; import com.mercedesbenz.sechub.commons.model.JSONConverterException; import com.mercedesbenz.sechub.commons.model.ScanType; import com.mercedesbenz.sechub.commons.model.SecHubConfigurationModel; +import com.mercedesbenz.sechub.commons.model.template.TemplateType; +import com.mercedesbenz.sechub.commons.pds.data.PDSTemplateMetaData; +import com.mercedesbenz.sechub.commons.pds.data.PDSTemplateMetaData.PDSAssetData; import com.mercedesbenz.sechub.commons.pds.execution.ExecutionEventData; import com.mercedesbenz.sechub.commons.pds.execution.ExecutionEventDetailIdentifier; import com.mercedesbenz.sechub.commons.pds.execution.ExecutionEventType; @@ -47,18 +52,24 @@ import com.mercedesbenz.sechub.pds.config.PDSServerConfigurationService; import com.mercedesbenz.sechub.pds.storage.PDSMultiStorageService; import com.mercedesbenz.sechub.pds.storage.PDSStorageInfoCollector; +import com.mercedesbenz.sechub.pds.usecase.PDSStep; +import com.mercedesbenz.sechub.pds.usecase.UseCaseSystemExecutesJob; import com.mercedesbenz.sechub.pds.util.PDSArchiveSupportProvider; import com.mercedesbenz.sechub.pds.util.PDSResilientRetryExecutor; import com.mercedesbenz.sechub.pds.util.PDSResilientRetryExecutor.ExceptionThrower; +import com.mercedesbenz.sechub.storage.core.AssetStorage; import com.mercedesbenz.sechub.storage.core.JobStorage; +import com.mercedesbenz.sechub.storage.core.Storage; @Service public class PDSWorkspaceService { public static final String UPLOAD = "upload"; + public static final String ASSETS = "assets"; public static final String EXTRACTED = "extracted"; public static final String EXTRACTED_SOURCES = EXTRACTED + "/sources"; public static final String EXTRACTED_BINARIES = EXTRACTED + "/binaries"; + public static final String EXTRACTED_ASSETS = EXTRACTED + "/assets"; public static final String OUTPUT = "output"; public static final String MESSAGES = "messages"; @@ -120,6 +131,9 @@ public class PDSWorkspaceService { @Autowired PDSWorkspacePreparationResultCalculator preparationResultCalculator; + @Autowired + CheckSumSupport checksumSupport; + private static final ArchiveFilter TAR_FILE_FILTER = new TarFileFilter(); private static final ArchiveFilter ZIP_FILE_FILTER = new SourcecodeZipFileFilter(); @@ -151,6 +165,7 @@ public void throwException(String message, Exception cause) throws IOException { * @return {@link PDSWorkspacePreparationResult}, never null * @throws IOException */ + @UseCaseSystemExecutesJob(@PDSStep(number = 4, name = "PDS workspace preparation", description = "PDS job workspace is prepared here: Directories are created, files are downloaded and extracted etc.")) public PDSWorkspacePreparationResult prepare(UUID pdsJobUUID, PDSJobConfiguration config, String metaData) throws IOException { PDSJobConfigurationSupport configurationSupport = new PDSJobConfigurationSupport(config); @@ -162,6 +177,8 @@ public PDSWorkspacePreparationResult prepare(UUID pdsJobUUID, PDSJobConfiguratio writeMetaData(pdsJobUUID, metaData); + importAndExtractFilesFromAssetStorage(pdsJobUUID, config, configurationSupport, preparationContext); + importWantedFilesFromJobStorage(pdsJobUUID, config, configurationSupport, preparationContext); extractZipFileUploadsWhenConfigured(pdsJobUUID, config, preparationContext); @@ -188,7 +205,7 @@ private void importWantedFilesFromJobStorage(UUID pdsJobUUID, PDSJobConfiguratio PDSResilientRetryExecutor resilientStorageReadExecutor = createResilientReadExecutor(preparationContext); File jobFolder = getUploadFolder(pdsJobUUID); - JobStorage storage = fetchStorage(pdsJobUUID, config); + JobStorage storage = fetchJobStorage(pdsJobUUID, config); Set names = resilientStorageReadExecutor.execute(() -> storage.listNames(), "List storage names for job: " + pdsJobUUID.toString()); @@ -209,6 +226,79 @@ private void importWantedFilesFromJobStorage(UUID pdsJobUUID, PDSJobConfiguratio } finally { storage.close(); } + + } + + private void importAndExtractFilesFromAssetStorage(UUID pdsJobUUID, PDSJobConfiguration config, PDSJobConfigurationSupport configurationSupport, + PDSWorkspacePreparationContext preparationContext) throws IOException { + + List templateMetaDataList = configurationSupport.getTemplateMetaData(); + if (templateMetaDataList.isEmpty()) { + LOG.debug("No template meta data available - will skipp asset data import"); + return; + } + + PDSResilientRetryExecutor resilientStorageReadExecutor = createResilientReadExecutor(preparationContext); + + for (PDSTemplateMetaData metaData : templateMetaDataList) { + TemplateType templateType = requireNonNull(metaData.getTemplateType(), "Template type may not be null"); + PDSAssetData assetData = requireNonNull(metaData.getAssetData(), "Asset data may not be null"); + String fileName = requireNonBlank(assetData.getFileName(), "Filename must be defined"); + String assetId = requireNonBlank(assetData.getAssetId(), "Asset id must be defined"); + String checksumFromSecHub = requireNonBlank(assetData.getChecksum(), "Checksum must be defined."); + + File assetDownloadFile = getAssetFileFromUpload(pdsJobUUID, assetId, fileName); + String assetFilePath = assetId + "/" + fileName; + + resilientStorageReadExecutor.execute(() -> { + + try (AssetStorage storage = storageService.createAssetStorage(assetId)) { + InputStream storageStream = storage.fetch(fileName); + readAndCopyInputStreamToFileSystem(pdsJobUUID, fileName, assetDownloadFile, storageStream); + + String checksumAfterDownloadFromStorage = checksumSupport.createSha256Checksum(assetDownloadFile.toPath()); + + if (!checksumAfterDownloadFromStorage.equals(checksumFromSecHub)) { + throw new IOException("Checksum not as expected for asset file:" + assetFilePath + "\nSecHub checksum:" + checksumFromSecHub + + ", Download checksum:" + checksumAfterDownloadFromStorage); + } + } + }, "Read and copy asset file: " + assetFilePath + " for job: " + pdsJobUUID); + + File extractionFolder = getAssetExtractionFolder(pdsJobUUID, templateType); + if (fileName.toLowerCase().endsWith(".zip")) { + LOG.info("Start extraction of asset file '{}'", fileName); + /* extract ZIP file */ + ArchiveExtractionConstraints archiveExtractionConstraints = createExtractionContraints(); + archiveSupportProvider.getArchiveSupport().extractFileAsIsToFolder(ArchiveType.ZIP, assetDownloadFile, extractionFolder, + archiveExtractionConstraints); + } else { + /* + * This case will only happen in PDS solution development: + * + * At development time, it can happen that there is a need to directly upload + * and change asset files in storage to make things easier/faster turn around + * times. + * + * But SecHub will always use "$pdsProductIdentifier.zip" as the asset file name + * for the product (if there exists one in storage) and send this inside + * parameters! + */ + LOG.warn( + "Asset file name '{}' does not end with '.zip' - will just copy the file to extraction folder. This may ONLY happen at PDS solution development time, but not in production by SecHub!", + fileName); + FileUtils.copyFile(assetDownloadFile, new File(extractionFolder, fileName)); + } + + } + + } + + private static final String requireNonBlank(String target, String message) { + if (requireNonNull(target, message).isBlank()) { + throw new IllegalStateException(message); + } + return target; } private PDSResilientRetryExecutor createResilientReadExecutor(PDSWorkspacePreparationContext preparationContext) { @@ -220,30 +310,34 @@ private PDSResilientRetryExecutor createResilientReadExecutor(PDSWo return resilientExecutor; } - private void readAndCopyStorageToFileSystem(UUID jobUUID, File jobFolder, JobStorage storage, String name) throws IOException { + private void readAndCopyStorageToFileSystem(UUID jobUUID, File jobFolder, Storage storage, String fileName) throws IOException { - File uploadFile = new File(jobFolder, name); + File uploadFile = new File(jobFolder, fileName); - try (InputStream fetchedInputStream = storage.fetch(name)) { + try (InputStream fetchedInputStream = storage.fetch(fileName)) { - try { + readAndCopyInputStreamToFileSystem(jobUUID, fileName, uploadFile, fetchedInputStream); + } - FileUtils.copyInputStreamToFile(fetchedInputStream, uploadFile); + } - LOG.debug("Imported '{}' for job {} from storage to {}", name, jobUUID, uploadFile.getAbsolutePath()); + private void readAndCopyInputStreamToFileSystem(UUID jobUUID, String fileName, File uploadFile, InputStream fetchedInputStream) throws IOException { + try { - } catch (IOException e) { + FileUtils.copyInputStreamToFile(fetchedInputStream, uploadFile); - LOG.error("Was not able to copy stream of uploaded file: {} for job {}, reason: ", name, jobUUID, e.getMessage()); + LOG.debug("Imported '{}' for job {} from storage to {}", fileName, jobUUID, uploadFile.getAbsolutePath()); - if (uploadFile.exists()) { - boolean deleteSuccessful = uploadFile.delete(); - LOG.info("Uploaded file existed. Deleted successfully: {}", deleteSuccessful); - } - throw e; + } catch (IOException e) { + + LOG.error("Was not able to copy stream of uploaded file: {} for job {}, reason: ", fileName, jobUUID, e.getMessage()); + + if (uploadFile.exists()) { + boolean deleteSuccessful = uploadFile.delete(); + LOG.info("Uploaded file existed. Deleted successfully: {}", deleteSuccessful); } + throw e; } - } void extractZipFileUploadsWhenConfigured(UUID jobUUID, PDSJobConfiguration config, PDSWorkspacePreparationContext preparationContext) throws IOException { @@ -288,7 +382,7 @@ private boolean isWantedStorageContent(String name, PDSJobConfigurationSupport c return false; } - private JobStorage fetchStorage(UUID pdsJobUUID, PDSJobConfiguration config) { + private JobStorage fetchJobStorage(UUID pdsJobUUID, PDSJobConfiguration config) { UUID pdsOrSecHubJobUUID; String storagePath; @@ -325,6 +419,41 @@ public File getUploadFolder(UUID pdsJobUUID) { return file; } + /** + * Resolves assets folder - if not existing it will be created + * + * @param pdsJobUUID + * @return assets folder + */ + private Path getAssetUploadFolder(UUID pdsJobUUID) { + File file = new File(getUploadFolder(pdsJobUUID), ASSETS); + file.mkdirs(); + return file.toPath(); + } + + /** + * Resolves asset extraction folder - if not existing it will be created + * + * @param pdsJobUUID + * @param templateType + * @return asset extraction folder + */ + public File getAssetExtractionFolder(UUID pdsJobUUID, TemplateType templateType) { + Path parent = getUploadFolder(pdsJobUUID).toPath(); + Path assetsExtractedPath = parent.resolve(EXTRACTED_ASSETS); + Path templateTypeExtractedPath = assetsExtractedPath.resolve(templateType.getId()); + File file = templateTypeExtractedPath.toFile(); + file.mkdirs(); + return file; + } + + private File getAssetFileFromUpload(UUID pdsJobUUID, String assetId, String fileName) { + Path parent = getAssetUploadFolder(pdsJobUUID); + Path assetIdDownloadPath = parent.resolve(assetId); + assetIdDownloadPath.toFile().mkdirs(); + return assetIdDownloadPath.resolve(fileName).toFile(); + } + private Path getWorkspaceFolderPath(UUID jobUUID) { File workspaceFolder = getWorkspaceFolder(jobUUID); Path workspaceFolderPath = workspaceFolder.toPath(); @@ -418,8 +547,7 @@ private boolean extractArchives(UUID pdsJobUUID, boolean deleteOriginFiles, SecH } ArchiveSupport archiveSupport = archiveSupportProvider.getArchiveSupport(); - ArchiveExtractionConstraints archiveExtractionConstraints = new ArchiveExtractionConstraints(archiveExtractionMaxFileSizeUncompressed, - archiveExtractionMaxEntries, archiveExtractionMaxDirectoryDepth, archiveExtractionTimeout); + ArchiveExtractionConstraints archiveExtractionConstraints = createExtractionContraints(); for (File archiveFile : archiveFiles) { try (FileInputStream archiveFileInputStream = new FileInputStream(archiveFile)) { @@ -443,6 +571,12 @@ private boolean extractArchives(UUID pdsJobUUID, boolean deleteOriginFiles, SecH return true; } + private ArchiveExtractionConstraints createExtractionContraints() { + ArchiveExtractionConstraints archiveExtractionConstraints = new ArchiveExtractionConstraints(archiveExtractionMaxFileSizeUncompressed, + archiveExtractionMaxEntries, archiveExtractionMaxDirectoryDepth, archiveExtractionTimeout); + return archiveExtractionConstraints; + } + public void cleanup(UUID jobUUID, PDSJobConfiguration config) throws IOException { FileUtils.deleteDirectory(getWorkspaceFolder(jobUUID)); LOG.info("Removed workspace folder for job {}", jobUUID); @@ -453,7 +587,7 @@ public void cleanup(UUID jobUUID, PDSJobConfiguration config) throws IOException LOG.info("Removed NOT storage for PDS job {} because sechub storage and will be handled by sechub job {}", jobUUID, config.getSechubJobUUID()); } else { - JobStorage storage = fetchStorage(jobUUID, config); + JobStorage storage = fetchJobStorage(jobUUID, config); storage.deleteAll(); LOG.info("Removed storage for job {}", jobUUID); storage.close(); @@ -549,6 +683,7 @@ public WorkspaceLocationData createLocationData(UUID jobUUID) { locationData.extractedSourcesLocation = createExtractedSourcesLocation(workspaceFolderPath).toString(); locationData.extractedBinariesLocation = createExtractedBinariesLocation(workspaceFolderPath).toString(); + locationData.extractedAssetsLocation = createExtractedAssetsLocation(workspaceFolderPath).toString(); locationData.sourceCodeZipFileLocation = createSourceCodeZipFileLocation(workspaceFolderPath).toString(); locationData.binariesTarFileLocation = createBinariesTarFileLocation(workspaceFolderPath).toString(); @@ -679,6 +814,10 @@ private Path createExtractedSourcesLocation(Path workspaceFolderPath) { return createWorkspacePathAndEnsureParentDirectories(workspaceFolderPath, UPLOAD + "/" + EXTRACTED_SOURCES); } + private Path createExtractedAssetsLocation(Path workspaceFolderPath) { + return createWorkspacePathAndEnsureParentDirectories(workspaceFolderPath, UPLOAD + "/" + EXTRACTED_ASSETS); + } + private Path createWorkspacePathAndEnsureDirectory(Path workspaceLocation, String subPath) { Path path = createWorkspacePathAndEnsureParentDirectories(workspaceLocation, subPath); if (!Files.exists(path)) { diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/WorkspaceLocationData.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/WorkspaceLocationData.java index 6ee704ba75..36a386f261 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/WorkspaceLocationData.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/job/WorkspaceLocationData.java @@ -11,6 +11,7 @@ public class WorkspaceLocationData { String userMessagesLocation; String metaDataFileLocation; String eventsLocation; + String extractedAssetsLocation; public String getWorkspaceLocation() { return workspaceLocation; @@ -47,4 +48,8 @@ public String getMetaDataFileLocation() { public String getEventsLocation() { return eventsLocation; } + + public String getExtractedAssetsLocation() { + return extractedAssetsLocation; + } } diff --git a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/storage/PDSMultiStorageService.java b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/storage/PDSMultiStorageService.java index 9ed628b700..efdbd681ef 100644 --- a/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/storage/PDSMultiStorageService.java +++ b/sechub-pds/src/main/java/com/mercedesbenz/sechub/pds/storage/PDSMultiStorageService.java @@ -9,6 +9,8 @@ import org.springframework.stereotype.Service; import com.mercedesbenz.sechub.pds.config.PDSServerConfigurationService; +import com.mercedesbenz.sechub.storage.core.AssetStorage; +import com.mercedesbenz.sechub.storage.core.AssetStorageFactory; import com.mercedesbenz.sechub.storage.core.JobStorage; import com.mercedesbenz.sechub.storage.core.JobStorageFactory; import com.mercedesbenz.sechub.storage.core.S3Setup; @@ -34,22 +36,30 @@ public class PDSMultiStorageService implements StorageService { private static final Logger LOG = LoggerFactory.getLogger(PDSMultiStorageService.class); private JobStorageFactory jobStorageFactory; + private AssetStorageFactory assetStorageFactory; @Autowired public PDSMultiStorageService(SharedVolumeSetup sharedVolumeSetup, S3Setup s3Setup) { if (s3Setup.isAvailable()) { - jobStorageFactory = new AwsS3JobStorageFactory(s3Setup); + AwsS3JobStorageFactory awsJobFactory = new AwsS3JobStorageFactory(s3Setup); + + jobStorageFactory = awsJobFactory; + assetStorageFactory = awsJobFactory; } else if (sharedVolumeSetup.isAvailable()) { - jobStorageFactory = new SharedVolumeJobStorageFactory(sharedVolumeSetup); + SharedVolumeJobStorageFactory sharedVolumeStorageFactory = new SharedVolumeJobStorageFactory(sharedVolumeSetup); + + jobStorageFactory = sharedVolumeStorageFactory; + assetStorageFactory = sharedVolumeStorageFactory; } - if (jobStorageFactory == null) { + if (jobStorageFactory == null || assetStorageFactory == null) { throw new IllegalStateException("Did not found any available storage setup! At least one must be set!"); } LOG.info("Created storage factory: {}", jobStorageFactory.getClass().getSimpleName()); + LOG.info("Created asset storage factory: {}", assetStorageFactory.getClass().getSimpleName()); } @@ -64,4 +74,9 @@ public JobStorage createJobStorageForPath(String storagePath, UUID jobUUID) { return jobStorage; } + @Override + public AssetStorage createAssetStorage(String assetId) { + return assetStorageFactory.createAssetStorage(assetId); + } + } \ No newline at end of file diff --git a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSWorkspaceServiceTest.java b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSWorkspaceServiceTest.java index d7791fd554..220567d65f 100644 --- a/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSWorkspaceServiceTest.java +++ b/sechub-pds/src/test/java/com/mercedesbenz/sechub/pds/job/PDSWorkspaceServiceTest.java @@ -3,27 +3,37 @@ import static com.mercedesbenz.sechub.test.TestConstants.*; import static java.io.File.*; -import static org.junit.jupiter.api.Assertions.*; +import static org.assertj.core.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Collections; +import java.util.List; import java.util.UUID; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import com.mercedesbenz.sechub.commons.core.security.CheckSumSupport; +import com.mercedesbenz.sechub.commons.model.JSONConverter; +import com.mercedesbenz.sechub.commons.model.template.TemplateType; import com.mercedesbenz.sechub.commons.pds.PDSDefaultParameterKeyConstants; +import com.mercedesbenz.sechub.commons.pds.data.PDSTemplateMetaData; +import com.mercedesbenz.sechub.commons.pds.data.PDSTemplateMetaData.PDSAssetData; import com.mercedesbenz.sechub.pds.commons.core.config.PDSProductSetup; import com.mercedesbenz.sechub.pds.config.PDSServerConfigurationService; import com.mercedesbenz.sechub.pds.execution.PDSExecutionParameterEntry; import com.mercedesbenz.sechub.pds.storage.PDSMultiStorageService; import com.mercedesbenz.sechub.pds.storage.PDSStorageInfoCollector; +import com.mercedesbenz.sechub.storage.core.AssetStorage; import com.mercedesbenz.sechub.storage.core.JobStorage; import com.mercedesbenz.sechub.test.TestFileReader; import com.mercedesbenz.sechub.test.TestUtil; @@ -41,6 +51,7 @@ class PDSWorkspaceServiceTest { private PDSWorkspacePreparationResultCalculator preparationResultCalculator; private PDSWorkspacePreparationContext preparationContext; private UUID jobUUID; + private CheckSumSupport checksumSupport; @BeforeAll static void beforeAll() throws IOException { @@ -51,14 +62,15 @@ static void beforeAll() throws IOException { void beforeEach() { jobUUID = UUID.randomUUID(); - storageService = mock(PDSMultiStorageService.class); - storage = mock(JobStorage.class); - storageInfoCollector = mock(PDSStorageInfoCollector.class); - preparationContextFactory = mock(PDSWorkspacePreparationContextFactory.class); - serverConfigService = mock(PDSServerConfigurationService.class); - preparationResultCalculator = mock(PDSWorkspacePreparationResultCalculator.class); + storageService = mock(); + storage = mock(); + storageInfoCollector = mock(); + preparationContextFactory = mock(); + serverConfigService = mock(); + preparationResultCalculator = mock(); + checksumSupport = mock(); - preparationContext = mock(PDSWorkspacePreparationContext.class); + preparationContext = mock(); when(preparationContextFactory.createPreparationContext(any())).thenReturn(preparationContext); PDSProductSetup setup = new PDSProductSetup(); @@ -73,6 +85,7 @@ void beforeEach() { serviceToTest.preparationContextFactory = preparationContextFactory; serviceToTest.serverConfigService = serverConfigService; serviceToTest.preparationResultCalculator = preparationResultCalculator; + serviceToTest.checksumSupport = checksumSupport; config = new PDSJobConfiguration(); @@ -145,7 +158,7 @@ void prepare_returns_result_from_calculator() throws Exception { PDSWorkspacePreparationResult result = serviceToTest.prepare(jobUUID, config, null); /* test */ - assertSame(expected, result); + assertThat(expected).isSameAs(result); } @Test @@ -156,7 +169,7 @@ void when_job_has_no_metadata_no_metadata_file_is_created_in_workspace() throws /* test */ File metaDataFile = serviceToTest.getMetaDataFile(jobUUID); - assertFalse(metaDataFile.exists()); + assertThat(metaDataFile.exists()).isFalse(); } @@ -168,8 +181,8 @@ void when_job_has_metadata_a_metadata_file_is_created_in_workspace_containing_co /* test */ File metaDataFile = serviceToTest.getMetaDataFile(jobUUID); - assertTrue(metaDataFile.exists()); - assertEquals("this is my metadata", TestFileReader.readTextFromFile(metaDataFile)); + assertThat(metaDataFile.exists()).isTrue(); + assertThat(TestFileReader.readTextFromFile(metaDataFile)).isEqualTo("this is my metadata"); } @Test @@ -182,14 +195,15 @@ void createLocationData_contains_expected_pathes_when_using_temp_directory_as_up String expectedWorspaceLocation = workspaceRootFolderPath + separatorChar + jobUUID; /* @formatter:off */ - assertEquals(expectedWorspaceLocation,result.getWorkspaceLocation()); - assertEquals(expectedWorspaceLocation+separatorChar+"output"+separatorChar+"result.txt",result.getResultFileLocation()); - assertEquals(expectedWorspaceLocation+separatorChar+"output"+separatorChar+"messages",result.getUserMessagesLocation()); - assertEquals(expectedWorspaceLocation+separatorChar+"metadata.txt",result.getMetaDataFileLocation()); - assertEquals(expectedWorspaceLocation+separatorChar+"upload"+separatorChar+SOURCECODE_ZIP,result.getSourceCodeZipFileLocation()); - assertEquals(expectedWorspaceLocation+separatorChar+"upload"+separatorChar+"extracted"+separatorChar+"sources",result.getExtractedSourcesLocation()); - assertEquals(expectedWorspaceLocation+separatorChar+"upload"+separatorChar+"extracted"+separatorChar+"binaries",result.getExtractedBinariesLocation()); - assertEquals(expectedWorspaceLocation+separatorChar+"events",result.getEventsLocation()); + assertThat(result.getWorkspaceLocation()).isEqualTo(expectedWorspaceLocation); + assertThat(result.getResultFileLocation()).isEqualTo(expectedWorspaceLocation+separatorChar+"output"+separatorChar+"result.txt"); + + assertThat(result.getUserMessagesLocation()).isEqualTo(expectedWorspaceLocation+separatorChar+"output"+separatorChar+"messages"); + assertThat(result.getMetaDataFileLocation()).isEqualTo(expectedWorspaceLocation+separatorChar+"metadata.txt"); + assertThat(result.getSourceCodeZipFileLocation()).isEqualTo(expectedWorspaceLocation+separatorChar+"upload"+separatorChar+SOURCECODE_ZIP); + assertThat(result.getExtractedSourcesLocation()).isEqualTo(expectedWorspaceLocation+separatorChar+"upload"+separatorChar+"extracted"+separatorChar+"sources"); + assertThat(result.getExtractedBinariesLocation()).isEqualTo(expectedWorspaceLocation+separatorChar+"upload"+separatorChar+"extracted"+separatorChar+"binaries"); + assertThat(result.getEventsLocation()).isEqualTo(expectedWorspaceLocation+separatorChar+"events"); /* @formatter:on */ } @@ -209,6 +223,69 @@ void when_configuration_tells_to_use_sechubstorage_sechub_storage_path_and_sechu verify(storageService).createJobStorageForPath("xyz/abc/project1", config.getSechubJobUUID()); } + @Test + void prepare_downloads_asset_and_stores_file_locally_when_parameter_contains_pds_template_metadata_no_checksum_failure() throws Exception { + /* prepare */ + PDSTemplateMetaData metaData = new PDSTemplateMetaData(); + metaData.setTemplateId("template1"); + metaData.setTemplateType(TemplateType.WEBSCAN_LOGIN); + PDSAssetData assetData = new PDSAssetData(); + assetData.setAssetId("asset1"); + assetData.setChecksum("checksum1"); + assetData.setFileName("file1.txt"); + metaData.setAssetData(assetData); + + String json = JSONConverter.get().toJSON(metaData, false); + config.getParameters().add(createEntry(PDSDefaultParameterKeyConstants.PARAM_KEY_PDS_CONFIG_TEMPLATE_META_DATA_LIST, json)); + + AssetStorage assetStorage = mock(); + when(storageService.createAssetStorage("asset1")).thenReturn(assetStorage); + when(assetStorage.fetch("file1.txt")).thenReturn(new ByteArrayInputStream("testdata".getBytes())); + when(checksumSupport.createSha256Checksum(any(Path.class))).thenReturn("checksum1"); + + /* execute */ + serviceToTest.prepare(jobUUID, config, null); + + /* test */ + ArgumentCaptor pathCaptor = ArgumentCaptor.captor(); + verify(storageService).createAssetStorage("asset1"); + verify(checksumSupport).createSha256Checksum(pathCaptor.capture()); + Path path = pathCaptor.getValue(); + + assertThat(path.getFileName().toString()).isEqualTo("file1.txt"); + + // check file is created + assertThat(Files.exists(path)).isTrue(); + List lines = Files.readAllLines(path); + assertThat(lines).contains("testdata").hasSize(1); + } + + @Test + void prepare_downloads_asset_and_stores_file_locally_when_parameter_contains_pds_template_metadata_checksum_failure() throws Exception { + /* prepare */ + PDSTemplateMetaData metaData = new PDSTemplateMetaData(); + metaData.setTemplateId("template1"); + metaData.setTemplateType(TemplateType.WEBSCAN_LOGIN); + PDSAssetData assetData = new PDSAssetData(); + assetData.setAssetId("asset1"); + assetData.setChecksum("checksum1"); + assetData.setFileName("file1.txt"); + metaData.setAssetData(assetData); + + String json = JSONConverter.get().toJSON(metaData, false); + config.getParameters().add(createEntry(PDSDefaultParameterKeyConstants.PARAM_KEY_PDS_CONFIG_TEMPLATE_META_DATA_LIST, json)); + + AssetStorage assetStorage = mock(); + when(storageService.createAssetStorage("asset1")).thenReturn(assetStorage); + when(assetStorage.fetch("file1.txt")).thenReturn(new ByteArrayInputStream("testdata".getBytes())); + when(checksumSupport.createSha256Checksum(any(Path.class))).thenReturn("checksum-other-means-failure"); + + /* execute + test */ + assertThatThrownBy(() -> serviceToTest.prepare(jobUUID, config, null)).cause().isInstanceOf(IOException.class) + .hasMessageStartingWith("Checksum not as expected"); + + } + private PDSExecutionParameterEntry createEntry(String key, String value) { PDSExecutionParameterEntry entry = new PDSExecutionParameterEntry(); entry.setKey(key); diff --git a/sechub-scan-product-checkmarx/src/main/java/com/mercedesbenz/sechub/domain/scan/product/checkmarx/CheckmarxExecutorConfigSuppport.java b/sechub-scan-product-checkmarx/src/main/java/com/mercedesbenz/sechub/domain/scan/product/checkmarx/CheckmarxExecutorConfigSuppport.java index 26cf7301f8..066af81fc3 100644 --- a/sechub-scan-product-checkmarx/src/main/java/com/mercedesbenz/sechub/domain/scan/product/checkmarx/CheckmarxExecutorConfigSuppport.java +++ b/sechub-scan-product-checkmarx/src/main/java/com/mercedesbenz/sechub/domain/scan/product/checkmarx/CheckmarxExecutorConfigSuppport.java @@ -9,6 +9,7 @@ import com.mercedesbenz.sechub.commons.core.environment.SystemEnvironmentVariableSupport; import com.mercedesbenz.sechub.commons.core.util.SimpleStringUtils; import com.mercedesbenz.sechub.commons.model.SecHubRuntimeException; +import com.mercedesbenz.sechub.domain.scan.product.ProductExecutorContext; import com.mercedesbenz.sechub.domain.scan.product.config.ProductExecutorConfig; import com.mercedesbenz.sechub.sharedkernel.error.NotAcceptableException; import com.mercedesbenz.sechub.sharedkernel.mapping.MappingIdentifier; @@ -27,14 +28,14 @@ public class CheckmarxExecutorConfigSuppport extends DefaultExecutorConfigSuppor * @return support * @throws NotAcceptableException when configuration is not valid */ - public static CheckmarxExecutorConfigSuppport createSupportAndAssertConfigValid(ProductExecutorConfig config, + public static CheckmarxExecutorConfigSuppport createSupportAndAssertConfigValid(ProductExecutorContext context, SystemEnvironmentVariableSupport variableSupport) { - return new CheckmarxExecutorConfigSuppport(config, variableSupport, new CheckmarxProductExecutorMinimumConfigValidation()); + return new CheckmarxExecutorConfigSuppport(context, variableSupport, new CheckmarxProductExecutorMinimumConfigValidation()); } - private CheckmarxExecutorConfigSuppport(ProductExecutorConfig config, SystemEnvironmentVariableSupport variableSupport, + private CheckmarxExecutorConfigSuppport(ProductExecutorContext context, SystemEnvironmentVariableSupport variableSupport, Validation validation) { - super(config, variableSupport, validation); + super(context, variableSupport, validation); } public boolean isAlwaysFullScanEnabled() { diff --git a/sechub-scan-product-checkmarx/src/main/java/com/mercedesbenz/sechub/domain/scan/product/checkmarx/CheckmarxProductExecutor.java b/sechub-scan-product-checkmarx/src/main/java/com/mercedesbenz/sechub/domain/scan/product/checkmarx/CheckmarxProductExecutor.java index b17cc43bc0..d1c640f636 100644 --- a/sechub-scan-product-checkmarx/src/main/java/com/mercedesbenz/sechub/domain/scan/product/checkmarx/CheckmarxProductExecutor.java +++ b/sechub-scan-product-checkmarx/src/main/java/com/mercedesbenz/sechub/domain/scan/product/checkmarx/CheckmarxProductExecutor.java @@ -109,8 +109,8 @@ protected List executeByAdapter(ProductExecutorData data) throws JobStorage storage = storageService.createJobStorageForProject(projectId, jobUUID); - CheckmarxExecutorConfigSuppport configSupport = CheckmarxExecutorConfigSuppport - .createSupportAndAssertConfigValid(data.getProductExecutorContext().getExecutorConfig(), systemEnvironmentVariableSupport); + CheckmarxExecutorConfigSuppport configSupport = CheckmarxExecutorConfigSuppport.createSupportAndAssertConfigValid(data.getProductExecutorContext(), + systemEnvironmentVariableSupport); AdapterMetaDataCallback metaDataCallback = data.getProductExecutorContext().getCallback(); diff --git a/sechub-scan-product-checkmarx/src/test/java/com/mercedesbenz/sechub/domain/scan/product/checkmarx/CheckmarxExecutorConfigSuppportTest.java b/sechub-scan-product-checkmarx/src/test/java/com/mercedesbenz/sechub/domain/scan/product/checkmarx/CheckmarxExecutorConfigSuppportTest.java index 7fee8f00c9..01b4215396 100644 --- a/sechub-scan-product-checkmarx/src/test/java/com/mercedesbenz/sechub/domain/scan/product/checkmarx/CheckmarxExecutorConfigSuppportTest.java +++ b/sechub-scan-product-checkmarx/src/test/java/com/mercedesbenz/sechub/domain/scan/product/checkmarx/CheckmarxExecutorConfigSuppportTest.java @@ -12,6 +12,7 @@ import com.mercedesbenz.sechub.adapter.checkmarx.CheckmarxConstants; import com.mercedesbenz.sechub.commons.core.environment.SystemEnvironmentVariableSupport; +import com.mercedesbenz.sechub.domain.scan.product.ProductExecutorContext; import com.mercedesbenz.sechub.domain.scan.product.config.ProductExecutorConfig; import com.mercedesbenz.sechub.domain.scan.product.config.ProductExecutorConfigSetup; import com.mercedesbenz.sechub.domain.scan.product.config.ProductExecutorConfigSetupJobParameter; @@ -23,12 +24,14 @@ public class CheckmarxExecutorConfigSuppportTest { private ProductExecutorConfigSetup setup; private List jobParameters; private SystemEnvironmentVariableSupport systemEnvironmentVariableSupport; + private ProductExecutorContext context; @Before public void before() throws Exception { - + context = mock(); systemEnvironmentVariableSupport = mock(SystemEnvironmentVariableSupport.class); config = mock(ProductExecutorConfig.class); + when(context.getExecutorConfig()).thenReturn(config); setup = mock(ProductExecutorConfigSetup.class); jobParameters = new ArrayList<>(); @@ -40,7 +43,7 @@ public void before() throws Exception { @Test public void client_secret_returns_default_when_not_configured() { /* prepare */ - supportToTest = CheckmarxExecutorConfigSuppport.createSupportAndAssertConfigValid(config, systemEnvironmentVariableSupport); + supportToTest = CheckmarxExecutorConfigSuppport.createSupportAndAssertConfigValid(context, systemEnvironmentVariableSupport); assertEquals(CheckmarxConstants.DEFAULT_CLIENT_SECRET, supportToTest.getClientSecret()); } @@ -49,7 +52,7 @@ public void client_secret_returns_default_when_not_configured() { public void client_secret_returns_configured_value_when_parameter_available() { /* prepare */ jobParameters.add(new ProductExecutorConfigSetupJobParameter(CheckmarxExecutorConfigParameterKeys.CHECKMARX_CLIENT_SECRET, "new.secret")); - supportToTest = CheckmarxExecutorConfigSuppport.createSupportAndAssertConfigValid(config, systemEnvironmentVariableSupport); + supportToTest = CheckmarxExecutorConfigSuppport.createSupportAndAssertConfigValid(context, systemEnvironmentVariableSupport); /* execute +test */ assertEquals("new.secret", supportToTest.getClientSecret()); @@ -59,7 +62,7 @@ public void client_secret_returns_configured_value_when_parameter_available() { public void client_secret_returns_an_empty_string_default_is_returned() { /* prepare */ jobParameters.add(new ProductExecutorConfigSetupJobParameter(CheckmarxExecutorConfigParameterKeys.CHECKMARX_CLIENT_SECRET, "")); - supportToTest = CheckmarxExecutorConfigSuppport.createSupportAndAssertConfigValid(config, systemEnvironmentVariableSupport); + supportToTest = CheckmarxExecutorConfigSuppport.createSupportAndAssertConfigValid(context, systemEnvironmentVariableSupport); /* execute +test */ assertEquals(CheckmarxConstants.DEFAULT_CLIENT_SECRET, supportToTest.getClientSecret()); @@ -68,7 +71,7 @@ public void client_secret_returns_an_empty_string_default_is_returned() { @Test public void engine_configuration_name_returns_default_when_not_configured() { /* prepare */ - supportToTest = CheckmarxExecutorConfigSuppport.createSupportAndAssertConfigValid(config, systemEnvironmentVariableSupport); + supportToTest = CheckmarxExecutorConfigSuppport.createSupportAndAssertConfigValid(context, systemEnvironmentVariableSupport); assertEquals(CheckmarxConstants.DEFAULT_CHECKMARX_ENGINECONFIGURATION_MULTILANGANGE_SCAN_NAME, supportToTest.getEngineConfigurationName()); } @@ -77,7 +80,7 @@ public void engine_configuration_name_returns_default_when_not_configured() { public void engine_configuration_name_returns_configured_value_when_parameter_available() { /* prepare */ jobParameters.add(new ProductExecutorConfigSetupJobParameter(CheckmarxExecutorConfigParameterKeys.CHECKMARX_ENGINE_CONFIGURATIONNAME, "test.engine")); - supportToTest = CheckmarxExecutorConfigSuppport.createSupportAndAssertConfigValid(config, systemEnvironmentVariableSupport); + supportToTest = CheckmarxExecutorConfigSuppport.createSupportAndAssertConfigValid(context, systemEnvironmentVariableSupport); /* execute +test */ assertEquals("test.engine", supportToTest.getEngineConfigurationName()); @@ -87,7 +90,7 @@ public void engine_configuration_name_returns_configured_value_when_parameter_av public void engine_configuration_name_returns_an_empty_string_default_is_returned() { /* prepare */ jobParameters.add(new ProductExecutorConfigSetupJobParameter(CheckmarxExecutorConfigParameterKeys.CHECKMARX_ENGINE_CONFIGURATIONNAME, "")); - supportToTest = CheckmarxExecutorConfigSuppport.createSupportAndAssertConfigValid(config, systemEnvironmentVariableSupport); + supportToTest = CheckmarxExecutorConfigSuppport.createSupportAndAssertConfigValid(context, systemEnvironmentVariableSupport); /* execute +test */ assertEquals(CheckmarxConstants.DEFAULT_CHECKMARX_ENGINECONFIGURATION_MULTILANGANGE_SCAN_NAME, supportToTest.getEngineConfigurationName()); @@ -97,7 +100,7 @@ public void engine_configuration_name_returns_an_empty_string_default_is_returne public void always_fullscan_enabled_true() { /* prepare */ jobParameters.add(new ProductExecutorConfigSetupJobParameter(CheckmarxExecutorConfigParameterKeys.CHECKMARX_FULLSCAN_ALWAYS, "true")); - supportToTest = CheckmarxExecutorConfigSuppport.createSupportAndAssertConfigValid(config, systemEnvironmentVariableSupport); + supportToTest = CheckmarxExecutorConfigSuppport.createSupportAndAssertConfigValid(context, systemEnvironmentVariableSupport); /* test */ assertEquals(true, supportToTest.isAlwaysFullScanEnabled()); @@ -107,7 +110,7 @@ public void always_fullscan_enabled_true() { public void always_fullscan_enabled_false() { /* prepare */ jobParameters.add(new ProductExecutorConfigSetupJobParameter(CheckmarxExecutorConfigParameterKeys.CHECKMARX_FULLSCAN_ALWAYS, "false")); - supportToTest = CheckmarxExecutorConfigSuppport.createSupportAndAssertConfigValid(config, systemEnvironmentVariableSupport); + supportToTest = CheckmarxExecutorConfigSuppport.createSupportAndAssertConfigValid(context, systemEnvironmentVariableSupport); /* test */ assertEquals(false, supportToTest.isAlwaysFullScanEnabled()); diff --git a/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/AbstractPDSProductExecutor.java b/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/AbstractPDSProductExecutor.java index d12908f390..56041bb67a 100644 --- a/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/AbstractPDSProductExecutor.java +++ b/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/AbstractPDSProductExecutor.java @@ -75,8 +75,7 @@ protected final List executeByAdapter(ProductExecutorData data) t LOG.debug("Trigger PDS adapter execution for scan type: {} by: {}", getScanType(), getClass().getSimpleName()); ProductExecutorContext executorContext = data.getProductExecutorContext(); - PDSExecutorConfigSupport configSupport = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(executorContext.getExecutorConfig(), - serviceCollection); + PDSExecutorConfigSupport configSupport = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(executorContext, serviceCollection); SecHubExecutionContext context = data.getSechubExecutionContext(); diff --git a/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSAdapterConfigurationStrategy.java b/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSAdapterConfigurationStrategy.java index 7693ce64bd..ed5dbf7d78 100644 --- a/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSAdapterConfigurationStrategy.java +++ b/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSAdapterConfigurationStrategy.java @@ -13,6 +13,7 @@ import com.mercedesbenz.sechub.adapter.AdapterConfigurationStrategy; import com.mercedesbenz.sechub.adapter.pds.PDSAdapterConfigurator; import com.mercedesbenz.sechub.adapter.pds.PDSAdapterConfiguratorProvider; +import com.mercedesbenz.sechub.commons.core.ConfigurationFailureException; import com.mercedesbenz.sechub.commons.model.ScanType; import com.mercedesbenz.sechub.commons.model.SecHubRuntimeException; import com.mercedesbenz.sechub.domain.scan.DefaultAdapterConfigurationStrategy; @@ -136,7 +137,7 @@ private PDSAdapterConfigurationStrategy(PDSAdapterConfigurationStrategyConfig co } @Override - public void configure(B configBuilder) { + public void configure(B configBuilder) throws ConfigurationFailureException { PDSAdapterConfigurator pdsConfigurator = null; if (configBuilder instanceof PDSAdapterConfiguratorProvider) { PDSAdapterConfiguratorProvider provider = (PDSAdapterConfiguratorProvider) configBuilder; @@ -153,13 +154,11 @@ public void configure( handlePdsParts(pdsConfigurator); } - private void handlePdsParts(PDSAdapterConfigurator pdsConfigurable) { + private void handlePdsParts(PDSAdapterConfigurator pdsConfigurable) throws ConfigurationFailureException { PDSExecutorConfigSupport configSupport = strategyConfig.configSupport; SecHubExecutionContext context = strategyConfig.productExecutorData.getSechubExecutionContext(); - Map jobParametersToSend = configSupport.createJobParametersToSendToPDS(context.getConfiguration()); - pdsConfigurable.setJobParameters(jobParametersToSend); pdsConfigurable.setReusingSecHubStorage(configSupport.isReusingSecHubStorage()); pdsConfigurable.setScanType(strategyConfig.scanType); pdsConfigurable.setPdsProductIdentifier(configSupport.getPDSProductIdentifier()); @@ -170,7 +169,6 @@ private void handlePdsParts(PDSAdapterConfigurator pdsConfigurable) { pdsConfigurable.setBinaryTarFileInputStreamOrNull(strategyConfig.binariesTarFileInputStreamOrNull); pdsConfigurable.setSourceCodeZipFileRequired(strategyConfig.contentProvider.isSourceRequired()); pdsConfigurable.setBinaryTarFileRequired(strategyConfig.contentProvider.isBinaryRequired()); - pdsConfigurable.setResilienceMaxRetries(configSupport.getPDSAdapterResilienceMaxRetries()); pdsConfigurable.setResilienceTimeToWaitBeforeRetryInMilliseconds(configSupport.getPDSAdapterResilienceRetryWaitInMilliseconds()); @@ -179,6 +177,14 @@ private void handlePdsParts(PDSAdapterConfigurator pdsConfigurable) { handleBinariesChecksum(pdsConfigurable); handleBinariesFileSize(pdsConfigurable); + + handleJobParameters(pdsConfigurable, configSupport, context); + } + + private void handleJobParameters(PDSAdapterConfigurator pdsConfigurable, PDSExecutorConfigSupport configSupport, SecHubExecutionContext context) + throws ConfigurationFailureException { + Map jobParametersToSend = configSupport.createJobParametersToSendToPDS(context); + pdsConfigurable.setJobParameters(jobParametersToSend); } private void handleSourceCodeChecksum(PDSAdapterConfigurator pdsConfigurable) { @@ -237,7 +243,7 @@ private void handleBinariesFileSize(PDSAdapterConfigurator pdsConfigurable) { } } - private void handleCommonParts(B configBuilder) { + private void handleCommonParts(B configBuilder) throws ConfigurationFailureException { /* standard configuration */ /* @formatter:off */ diff --git a/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSExecutorConfigSupport.java b/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSExecutorConfigSupport.java index a6dc958cc0..d119bbd8e4 100644 --- a/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSExecutorConfigSupport.java +++ b/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSExecutorConfigSupport.java @@ -14,19 +14,27 @@ import org.slf4j.LoggerFactory; import com.mercedesbenz.sechub.adapter.DefaultExecutorConfigSupport; +import com.mercedesbenz.sechub.commons.core.ConfigurationFailureException; import com.mercedesbenz.sechub.commons.core.environment.SystemEnvironmentVariableSupport; import com.mercedesbenz.sechub.commons.core.util.SecHubStorageUtil; import com.mercedesbenz.sechub.commons.core.util.SimpleStringUtils; +import com.mercedesbenz.sechub.commons.model.JSONConverter; +import com.mercedesbenz.sechub.commons.model.ScanType; import com.mercedesbenz.sechub.commons.model.SecHubDataConfigurationType; import com.mercedesbenz.sechub.commons.model.SecHubDataConfigurationTypeListParser; +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition; import com.mercedesbenz.sechub.commons.pds.PDSConfigDataKeyProvider; import com.mercedesbenz.sechub.commons.pds.PDSDefaultParameterKeyConstants; import com.mercedesbenz.sechub.commons.pds.PDSKey; import com.mercedesbenz.sechub.commons.pds.PDSKeyProvider; +import com.mercedesbenz.sechub.commons.pds.data.PDSTemplateMetaData; import com.mercedesbenz.sechub.domain.scan.NetworkTargetProductServerDataProvider; import com.mercedesbenz.sechub.domain.scan.NetworkTargetType; +import com.mercedesbenz.sechub.domain.scan.SecHubExecutionContext; import com.mercedesbenz.sechub.domain.scan.config.ScanMapping; +import com.mercedesbenz.sechub.domain.scan.product.ProductExecutorContext; import com.mercedesbenz.sechub.domain.scan.product.config.ProductExecutorConfig; +import com.mercedesbenz.sechub.sharedkernel.ProductIdentifier; import com.mercedesbenz.sechub.sharedkernel.configuration.SecHubConfiguration; import com.mercedesbenz.sechub.sharedkernel.error.NotAcceptableException; import com.mercedesbenz.sechub.sharedkernel.validation.Validation; @@ -42,6 +50,8 @@ public class PDSExecutorConfigSupport extends DefaultExecutorConfigSupport imple private PDSExecutorConfigSuppportServiceCollection serviceCollection; private SecHubDataConfigurationTypeListParser parser = new SecHubDataConfigurationTypeListParser(); + PDSTemplateMetaDataService templateMetaDataTransformer = new PDSTemplateMetaDataService(); + static { List> allParameterProviders = new ArrayList<>(); allParameterProviders.addAll(Arrays.asList(SecHubProductExecutionPDSKeyProvider.values())); @@ -64,29 +74,63 @@ public static List> getUnmodifiableListOfParame * @return support * @throws NotAcceptableException when configuration is not valid */ - public static PDSExecutorConfigSupport createSupportAndAssertConfigValid(ProductExecutorConfig config, + public static PDSExecutorConfigSupport createSupportAndAssertConfigValid(ProductExecutorContext context, PDSExecutorConfigSuppportServiceCollection serviceCollection) { - return new PDSExecutorConfigSupport(config, serviceCollection, new PDSProductExecutorMinimumConfigValidation()); + PDSExecutorConfigSupport result = new PDSExecutorConfigSupport(context, serviceCollection, new PDSProductExecutorMinimumConfigValidation()); + return result; } - private PDSExecutorConfigSupport(ProductExecutorConfig config, PDSExecutorConfigSuppportServiceCollection serviceCollection, + private PDSExecutorConfigSupport(ProductExecutorContext context, PDSExecutorConfigSuppportServiceCollection serviceCollection, Validation validation) { - super(config, serviceCollection.getSystemEnvironmentVariableSupport(), validation); + super(context, serviceCollection.getSystemEnvironmentVariableSupport(), validation); this.serviceCollection = serviceCollection; } - public Map createJobParametersToSendToPDS(SecHubConfiguration secHubConfiguration) { + public Map createJobParametersToSendToPDS(SecHubExecutionContext context) throws ConfigurationFailureException { + if (context == null) { + throw new IllegalArgumentException("context may not be null!"); + } + SecHubConfiguration configuration = context.getConfiguration(); + if (configuration == null) { + throw new IllegalStateException("configuration may not be null inside context at this moment!"); + } + ProductIdentifier productIdentifier = config.getProductIdentifier(); + if (productIdentifier == null) { + throw new IllegalStateException("productIdentifier may not be null inside config at this moment!"); + } + ScanType scanType = productIdentifier.getType(); + if (scanType == null) { + throw new IllegalStateException("scanType may not be null inside productIdentifier:" + productIdentifier); + } Map parametersToSend = createParametersToSendByProviders(keyProvidersForSendingParametersToPDS); handleEnvironmentVariablesInJobParameters(parametersToSend); /* handle remaining parts without environment variable conversion */ - handleSecHubStorageIfNecessary(secHubConfiguration, parametersToSend); + handleSecHubStorageIfNecessary(configuration, parametersToSend); addMappingsAsJobParameter(parametersToSend); + addPdsTemplateMetaDataList(scanType, context, parametersToSend); return parametersToSend; } + private void addPdsTemplateMetaDataList(ScanType scanType, SecHubExecutionContext context, Map parametersToSend) + throws ConfigurationFailureException { + List templateDefinitions = context.getTemplateDefinitions(); + if (templateDefinitions == null) { + throw new IllegalStateException("template definitions may not be null inside context at this moment!"); + } + PDSTemplateMetaDataService templateMetaDataService = serviceCollection.getTemplateMetaDataService(); + + List pdsTemplateMetaDataList = templateMetaDataService.createTemplateMetaData(templateDefinitions, getPDSProductIdentifier(), + scanType, context.getConfiguration()); + + templateMetaDataService.ensureTemplateAssetFilesAreAvailableInStorage(pdsTemplateMetaDataList); + + String pdsTemplateMetaDataListAsJson = JSONConverter.get().toJSON(pdsTemplateMetaDataList, false); + parametersToSend.put(PDSDefaultParameterKeyConstants.PARAM_KEY_PDS_CONFIG_TEMPLATE_META_DATA_LIST, pdsTemplateMetaDataListAsJson); + } + private void handleSecHubStorageIfNecessary(SecHubConfiguration secHubConfiguration, Map parametersToSend) { if (isReusingSecHubStorage()) { String projectId = secHubConfiguration.getProjectId(); @@ -310,4 +354,5 @@ public boolean isGivenStorageSupportedByPDSProduct(PDSStorageContentProvider con public String getDataTypesSupportedByPDSAsString() { return getParameter(PDSConfigDataKeyProvider.PDS_CONFIG_SUPPORTED_DATATYPES); } + } diff --git a/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSExecutorConfigSuppportServiceCollection.java b/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSExecutorConfigSuppportServiceCollection.java index 5bd6854a7b..b36d5113a8 100644 --- a/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSExecutorConfigSuppportServiceCollection.java +++ b/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSExecutorConfigSuppportServiceCollection.java @@ -16,6 +16,9 @@ public class PDSExecutorConfigSuppportServiceCollection { @Autowired ScanMappingRepository scanMappingRepository; + @Autowired + PDSTemplateMetaDataService templateMetaDataService; + public SystemEnvironmentVariableSupport getSystemEnvironmentVariableSupport() { return systemEnvironment; } @@ -23,4 +26,8 @@ public SystemEnvironmentVariableSupport getSystemEnvironmentVariableSupport() { public ScanMappingRepository getScanMappingRepository() { return scanMappingRepository; } + + public PDSTemplateMetaDataService getTemplateMetaDataService() { + return templateMetaDataService; + } } diff --git a/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSInfraScanProductExecutor.java b/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSInfraScanProductExecutor.java index cb1192bd72..a20c32b386 100644 --- a/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSInfraScanProductExecutor.java +++ b/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSInfraScanProductExecutor.java @@ -112,8 +112,7 @@ protected void customize(ProductExecutorData data) { data.setNetworkLocationProvider(new InfraScanNetworkLocationProvider(secHubConfiguration)); ProductExecutorContext executorContext = data.getProductExecutorContext(); - PDSExecutorConfigSupport configSupport = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(executorContext.getExecutorConfig(), - serviceCollection); + PDSExecutorConfigSupport configSupport = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(executorContext, serviceCollection); data.setNetworkTargetDataProvider(configSupport); } diff --git a/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSTemplateMetaDataService.java b/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSTemplateMetaDataService.java new file mode 100644 index 0000000000..b0acdd59e3 --- /dev/null +++ b/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSTemplateMetaDataService.java @@ -0,0 +1,116 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.product.pds; + +import java.util.ArrayList; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.mercedesbenz.sechub.commons.core.ConfigurationFailureException; +import com.mercedesbenz.sechub.commons.model.ScanType; +import com.mercedesbenz.sechub.commons.model.SecHubConfigurationModel; +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition; +import com.mercedesbenz.sechub.commons.pds.data.PDSTemplateMetaData; +import com.mercedesbenz.sechub.commons.pds.data.PDSTemplateMetaData.PDSAssetData; +import com.mercedesbenz.sechub.domain.scan.asset.AssetDetailData; +import com.mercedesbenz.sechub.domain.scan.asset.AssetFileData; +import com.mercedesbenz.sechub.domain.scan.asset.AssetService; +import com.mercedesbenz.sechub.sharedkernel.error.NotFoundException; + +@Service +public class PDSTemplateMetaDataService { + + private static final Logger LOG = LoggerFactory.getLogger(PDSTemplateMetaDataService.class); + + @Autowired + AssetService assetService; + + @Autowired + RelevantScanTemplateDefinitionFilter filter; + + public List createTemplateMetaData(List templateDefinitions, String pdsProductId, ScanType scanType, + SecHubConfigurationModel configuration) throws ConfigurationFailureException { + + List result = new ArrayList<>(); + + List filteredDefinitions = filter.filter(templateDefinitions, scanType, configuration); + if (filteredDefinitions.isEmpty()) { + LOG.debug("Given {} template definitions, after filtering: {}", templateDefinitions.size(), filteredDefinitions.size()); + } + + resolveAssetFileDataAndAddToResult(result, pdsProductId, filteredDefinitions); + + return result; + } + + /** + * Ensures that asset files in given PDS template meta data is available in + * storage. + * + * @param metaDataList list containing template meta data + * @throws ConfigurationFailureException + */ + public void ensureTemplateAssetFilesAreAvailableInStorage(List metaDataList) throws ConfigurationFailureException { + for (PDSTemplateMetaData metaData : metaDataList) { + + PDSAssetData assetData = metaData.getAssetData(); + + String assetId = assetData.getAssetId(); + String fileName = assetData.getFileName(); + + assetService.ensureAssetFileInStorageAvailableAndHasSameChecksumAsInDatabase(fileName, assetId); + + } + } + + private void resolveAssetFileDataAndAddToResult(List result, String pdsProductId, List filteredDefinitions) + throws ConfigurationFailureException { + if (filteredDefinitions.isEmpty()) { + return; + } + + String filename = pdsProductId + ".zip"; + + for (TemplateDefinition definition : filteredDefinitions) { + String assetId = definition.getAssetId(); + AssetDetailData details = null; + try { + details = assetService.fetchAssetDetails(assetId); + } catch (NotFoundException e) { + /* asset does not exist */ + throw new ConfigurationFailureException("The asset " + assetId + " does not exist! Cannot transform", e); + } + + List files = details.getFiles(); + boolean fileForTemplateFound = false; + + for (AssetFileData fileData : files) { + if (!filename.equals(fileData.getFileName())) { + continue; + } + /* found */ + PDSAssetData assetData = new PDSAssetData(); + assetData.setAssetId(assetId); + assetData.setFileName(fileData.getFileName()); + assetData.setChecksum(fileData.getChecksum()); + + PDSTemplateMetaData metaData = new PDSTemplateMetaData(); + metaData.setTemplateId(definition.getId()); + metaData.setTemplateType(definition.getType()); + metaData.setAssetData(assetData); + + result.add(metaData); + + fileForTemplateFound = true; + break; + } + + if (!fileForTemplateFound) { + throw new ConfigurationFailureException("The asset " + assetId + " does not contain file '" + filename + "'"); + } + } + } +} diff --git a/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSWebScanProductExecutor.java b/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSWebScanProductExecutor.java index b11947eac7..4e4478da99 100644 --- a/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSWebScanProductExecutor.java +++ b/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSWebScanProductExecutor.java @@ -110,8 +110,7 @@ protected void customize(ProductExecutorData data) { data.setNetworkLocationProvider(new WebScanNetworkLocationProvider(secHubConfiguration)); ProductExecutorContext executorContext = data.getProductExecutorContext(); - PDSExecutorConfigSupport configSupport = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(executorContext.getExecutorConfig(), - serviceCollection); + PDSExecutorConfigSupport configSupport = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(executorContext, serviceCollection); data.setNetworkTargetDataProvider(configSupport); diff --git a/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/RelevantScanTemplateDefinitionFilter.java b/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/RelevantScanTemplateDefinitionFilter.java new file mode 100644 index 0000000000..724f720403 --- /dev/null +++ b/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/RelevantScanTemplateDefinitionFilter.java @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.product.pds; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import com.mercedesbenz.sechub.commons.model.ScanType; +import com.mercedesbenz.sechub.commons.model.SecHubConfigurationModel; +import com.mercedesbenz.sechub.commons.model.template.TemplateData; +import com.mercedesbenz.sechub.commons.model.template.TemplateDataResolver; +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition; +import com.mercedesbenz.sechub.commons.model.template.TemplateType; + +@Component +/** + * Filters template definitions which are relevant for a scan + * + * @author Albert Tregnaghi + * + */ +public class RelevantScanTemplateDefinitionFilter { + + @Autowired + TemplateDataResolver templateDataResolver; + + /** + * Filters template definitions for only relevant definitions for given scan + * type and configuration. If the configuration does not contain a templateData + * definition, the list will always be empty. If configuration contains template + * data, but the scan type does not need/use them the result will also be empty + * + * @param templateDefinitions all given definitions + * @param scanType scan type for which relevant definitions shall be + * filtered + * @param configuration SecHub configuration which contains template data + * (or not) + * @return list of relevant template definitions for the scan, never + * null + */ + public List filter(List templateDefinitions, ScanType scanType, SecHubConfigurationModel configuration) { + List result = new ArrayList<>(); + + for (TemplateDefinition definition : templateDefinitions) { + + addDefinitionToResultIfNecessary(definition, scanType, configuration, result); + } + return result; + } + + private void addDefinitionToResultIfNecessary(TemplateDefinition definition, ScanType scanType, SecHubConfigurationModel configuration, + List result) { + if (scanType == null) { + return; + } + switch (scanType) { + case WEB_SCAN: + /* for web scan we accept WEBSCAN_LOGIN templates */ + if (TemplateType.WEBSCAN_LOGIN.equals(definition.getType())) { + TemplateData templateData = templateDataResolver.resolveTemplateData(TemplateType.WEBSCAN_LOGIN, configuration); + if (templateData != null) { + /* data available, so add to result */ + result.add(definition); + } + } + break; + default: + break; + } + + } + +} diff --git a/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/SecHubProductExecutionPDSKeyProvider.java b/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/SecHubProductExecutionPDSKeyProvider.java index 395e7fd580..2120e1a489 100644 --- a/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/SecHubProductExecutionPDSKeyProvider.java +++ b/sechub-scan-product-pds/src/main/java/com/mercedesbenz/sechub/domain/scan/product/pds/SecHubProductExecutionPDSKeyProvider.java @@ -8,7 +8,7 @@ import com.mercedesbenz.sechub.domain.scan.NetworkTargetType; /** - * These providers/keys are used by sechub PDS product executors at runtime + * These providers/keys are used by SecHub PDS product executors at runtime * while communicating with PDS servers. * * @author Albert Tregnaghi diff --git a/sechub-scan-product-pds/src/test/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSExecutorConfigSupportTest.java b/sechub-scan-product-pds/src/test/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSExecutorConfigSupportTest.java index 32049385b1..987d59c304 100644 --- a/sechub-scan-product-pds/src/test/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSExecutorConfigSupportTest.java +++ b/sechub-scan-product-pds/src/test/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSExecutorConfigSupportTest.java @@ -1,10 +1,13 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.domain.scan.product.pds; +import static org.assertj.core.api.Assertions.*; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; @@ -24,16 +27,25 @@ import com.mercedesbenz.sechub.commons.mapping.MappingData; import com.mercedesbenz.sechub.commons.mapping.MappingEntry; import com.mercedesbenz.sechub.commons.model.JSONConverter; +import com.mercedesbenz.sechub.commons.model.ScanType; import com.mercedesbenz.sechub.commons.model.SecHubDataConfigurationType; +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition; import com.mercedesbenz.sechub.commons.pds.PDSConfigDataKeyProvider; +import com.mercedesbenz.sechub.commons.pds.PDSDefaultParameterKeyConstants; +import com.mercedesbenz.sechub.commons.pds.data.PDSTemplateMetaData; +import com.mercedesbenz.sechub.commons.pds.data.PDSTemplateMetaData.PDSAssetData; import com.mercedesbenz.sechub.domain.scan.NetworkTargetType; +import com.mercedesbenz.sechub.domain.scan.SecHubExecutionContext; import com.mercedesbenz.sechub.domain.scan.config.ScanMapping; import com.mercedesbenz.sechub.domain.scan.config.ScanMappingRepository; +import com.mercedesbenz.sechub.domain.scan.product.ProductExecutorContext; import com.mercedesbenz.sechub.domain.scan.product.config.ProductExecutorConfig; import com.mercedesbenz.sechub.domain.scan.product.config.ProductExecutorConfigSetup; import com.mercedesbenz.sechub.domain.scan.product.config.ProductExecutorConfigSetupCredentials; import com.mercedesbenz.sechub.domain.scan.product.config.ProductExecutorConfigSetupJobParameter; +import com.mercedesbenz.sechub.sharedkernel.ProductIdentifier; import com.mercedesbenz.sechub.sharedkernel.configuration.SecHubConfiguration; +import com.mercedesbenz.sechub.test.TestCanaryException; public class PDSExecutorConfigSupportTest { @@ -59,11 +71,27 @@ public class PDSExecutorConfigSupportTest { private List jobParameters; private ScanMappingRepository repository; private SystemEnvironmentVariableSupport systemEnvironmentVariableSupport; + private ProductExecutorContext context; + private SecHubExecutionContext sechubExecutionContext; + private SecHubConfiguration sechubConfiguration; + private PDSTemplateMetaDataService templateMetaDataService; @BeforeEach public void before() throws Exception { - config = mock(ProductExecutorConfig.class); - executorConfigSetup = mock(ProductExecutorConfigSetup.class); + sechubConfiguration = mock(); + sechubExecutionContext = mock(); + config = mock(); + context = mock(); + executorConfigSetup = mock(); + serviceCollection = mock(); + repository = mock(); + templateMetaDataService = mock(); + + when(sechubExecutionContext.getTemplateDefinitions()).thenReturn(Collections.emptyList()); + when(sechubExecutionContext.getConfiguration()).thenReturn(sechubConfiguration); + + when(config.getProductIdentifier()).thenReturn(ProductIdentifier.UNKNOWN);// the scan type unknown is used, because not really relevant + when(context.getExecutorConfig()).thenReturn(config); jobParameters = new ArrayList<>(); jobParameters.add(new ProductExecutorConfigSetupJobParameter(PDSConfigDataKeyProvider.PDS_CONFIG_PRODUCTIDENTIFIER.getKey().getId(), @@ -82,16 +110,14 @@ public void before() throws Exception { when(executorConfigSetup.getJobParameters()).thenReturn(jobParameters); - serviceCollection = mock(PDSExecutorConfigSuppportServiceCollection.class); - Answer defaultAnswerWithNoConversion = createAnswerWhichReturnsAlwaysJustTheOriginValue(); systemEnvironmentVariableSupport = mock(SystemEnvironmentVariableSupport.class, defaultAnswerWithNoConversion); - repository = mock(ScanMappingRepository.class); - when(serviceCollection.getScanMappingRepository()).thenReturn(repository); when(serviceCollection.getSystemEnvironmentVariableSupport()).thenReturn(systemEnvironmentVariableSupport); - supportToTest = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(config, serviceCollection); + when(serviceCollection.getTemplateMetaDataService()).thenReturn(templateMetaDataService); + + supportToTest = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(context, serviceCollection); } @EnumSource(SecHubDataConfigurationType.class) @@ -107,7 +133,7 @@ void isGivenStorageSupportedByPDSProduct_binary_and_source_required_from_model_a .add(new ProductExecutorConfigSetupJobParameter(PDSConfigDataKeyProvider.PDS_CONFIG_SUPPORTED_DATATYPES.getKey().getId(), type.toString())); // create support again (necessary to have new job parameters included) - supportToTest = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(config, serviceCollection); + supportToTest = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(context, serviceCollection); /* execute */ boolean result = supportToTest.isGivenStorageSupportedByPDSProduct(contentProvider); @@ -143,7 +169,7 @@ void isGivenStorageSupportedByPDSProduct_no_binary_in_model_required_but_product SecHubDataConfigurationType.BINARY.toString())); // create support again (necessary to have new job parameters included) - supportToTest = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(config, serviceCollection); + supportToTest = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(context, serviceCollection); /* execute */ boolean result = supportToTest.isGivenStorageSupportedByPDSProduct(contentProvider); @@ -164,7 +190,7 @@ void isGivenStorageSupportedByPDSProduct_only_source_in_model_required_but_produ jobParameters.add(new ProductExecutorConfigSetupJobParameter(PDSConfigDataKeyProvider.PDS_CONFIG_SUPPORTED_DATATYPES.getKey().getId(), typesAsString)); // create support again (necessary to have new job parameters included) - supportToTest = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(config, serviceCollection); + supportToTest = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(context, serviceCollection); /* execute */ boolean result = supportToTest.isGivenStorageSupportedByPDSProduct(contentProvider); @@ -185,7 +211,7 @@ void isGivenStorageSupportedByPDSProduct_only_binary_in_model_required_but_produ jobParameters.add(new ProductExecutorConfigSetupJobParameter(PDSConfigDataKeyProvider.PDS_CONFIG_SUPPORTED_DATATYPES.getKey().getId(), typesAsString)); // create support again (necessary to have new job parameters included) - supportToTest = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(config, serviceCollection); + supportToTest = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(context, serviceCollection); /* execute */ boolean result = supportToTest.isGivenStorageSupportedByPDSProduct(contentProvider); @@ -206,7 +232,7 @@ void isGivenStorageSupportedByPDSProduct_no_source_in_model_required_but_product SecHubDataConfigurationType.SOURCE.toString())); // create support again (necessary to have new job parameters included) - supportToTest = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(config, serviceCollection); + supportToTest = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(context, serviceCollection); /* execute */ boolean result = supportToTest.isGivenStorageSupportedByPDSProduct(contentProvider); @@ -227,7 +253,7 @@ void isGivenStorageSupportedByPDSProduct_no_source_or_binary_in_model_required_b SecHubDataConfigurationType.NONE.toString())); // create support again (necessary to have new job parameters included) - supportToTest = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(config, serviceCollection); + supportToTest = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(context, serviceCollection); /* execute */ boolean result = supportToTest.isGivenStorageSupportedByPDSProduct(contentProvider); @@ -249,7 +275,7 @@ void isGivenStorageSupportedByPDSProduct_no_source_or_binary_in_model_required_b .add(new ProductExecutorConfigSetupJobParameter(PDSConfigDataKeyProvider.PDS_CONFIG_SUPPORTED_DATATYPES.getKey().getId(), type.toString())); // create support again (necessary to have new job parameters included) - supportToTest = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(config, serviceCollection); + supportToTest = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(context, serviceCollection); /* execute */ boolean result = supportToTest.isGivenStorageSupportedByPDSProduct(contentProvider); @@ -272,7 +298,7 @@ void isGivenStorageSupportedByPDSProduct_no_source_or_binary_in_model_but_wrong_ jobParameters.add(new ProductExecutorConfigSetupJobParameter(PDSConfigDataKeyProvider.PDS_CONFIG_SUPPORTED_DATATYPES.getKey().getId(), typesAsString)); // create support again (necessary to have new job parameters included) - supportToTest = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(config, serviceCollection); + supportToTest = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(context, serviceCollection); /* execute */ boolean result = supportToTest.isGivenStorageSupportedByPDSProduct(contentProvider); @@ -297,7 +323,25 @@ void isTargetTypeForbidden_returns_false_for_target_type_requested_is_intranet_w } @Test - void createJobParametersToSendToPDS_environmentVariablesEntriesAreReplacedWithTheirContent() { + void createJobParametersToSendToPDS_null_list_of_template_meta_data_throws_illegal_argument_exception() { + assertThatThrownBy(() -> supportToTest.createJobParametersToSendToPDS(null)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void createJobParametersToSendToPDS_empty_template_list_is_accepted() throws Exception { + /* prepare */ + when(sechubExecutionContext.getTemplateDefinitions()).thenReturn(Collections.emptyList()); + + /* execute */ + Map result = supportToTest.createJobParametersToSendToPDS(sechubExecutionContext); + + /* test */ + String data = result.get(PDSDefaultParameterKeyConstants.PARAM_KEY_PDS_CONFIG_TEMPLATE_META_DATA_LIST); + assertThat(data).isEqualTo("[]"); + } + + @Test + void createJobParametersToSendToPDS_environmentVariablesEntriesAreReplacedWithTheirContent() throws Exception { /* prepare */ String parameterKey1 = "test.key1"; String parameterKey2 = "test.key2"; @@ -308,15 +352,13 @@ void createJobParametersToSendToPDS_environmentVariablesEntriesAreReplacedWithTh jobParameters.add(new ProductExecutorConfigSetupJobParameter(parameterKey3, "just-a-key-not-converted")); // create support again (necessary to have new job parameters included) - supportToTest = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(config, serviceCollection); + supportToTest = PDSExecutorConfigSupport.createSupportAndAssertConfigValid(context, serviceCollection); when(systemEnvironmentVariableSupport.getValueOrVariableContent("env:A_TESTVARIABLE")).thenReturn("resolved-value"); when(systemEnvironmentVariableSupport.getValueOrVariableContent("env:A_NOT_EXISTING_VARIABLE")).thenReturn(null); - SecHubConfiguration sechubConfiguration = mock(SecHubConfiguration.class); - /* execute */ - Map parameterMap = supportToTest.createJobParametersToSendToPDS(sechubConfiguration); + Map parameterMap = supportToTest.createJobParametersToSendToPDS(sechubExecutionContext); /* test */ assertEquals("resolved-value", parameterMap.get(parameterKey1)); @@ -326,15 +368,13 @@ void createJobParametersToSendToPDS_environmentVariablesEntriesAreReplacedWithTh } @Test - void createJobParametersToSendToPDS_mapping_is_resolved() { + void createJobParametersToSendToPDS_mapping_is_resolved() throws Exception { /* prepare */ mockSecHubMappingInDatabase(); mockSecHubMappingId2InDatabase(); - SecHubConfiguration sechubConfiguration = new SecHubConfiguration(); - /* execute */ - Map parameters = supportToTest.createJobParametersToSendToPDS(sechubConfiguration); + Map parameters = supportToTest.createJobParametersToSendToPDS(sechubExecutionContext); /* test 1 */ String p1 = parameters.get(SECHUB_MAPPING_ID_1); @@ -385,6 +425,58 @@ void createJobParametersToSendToPDS_mapping_is_resolved() { verify(systemEnvironmentVariableSupport, times(1)).getValueOrVariableContent(any()); } + @Test + void createJobParametersToSendToPDS_pds_template_meta_data_is_created_by_result_from_template_metadata_service() throws Exception { + /* prepare */ + List templateDefinitions = mock(); + when(sechubExecutionContext.getTemplateDefinitions()).thenReturn(templateDefinitions); + + List templateMetaDataServiceResult = createTemplateMetaDataServiceExampleResult(); + + when(templateMetaDataService.createTemplateMetaData(eq(templateDefinitions), anyString(), any(ScanType.class), eq(sechubConfiguration))) + .thenReturn(templateMetaDataServiceResult); + /* execute */ + Map parameterMap = supportToTest.createJobParametersToSendToPDS(sechubExecutionContext); + + /* test */ + String expectedJson = JSONConverter.get().toJSON(templateMetaDataServiceResult, false); + String jsonFromConfigSupport = parameterMap.get(PDSDefaultParameterKeyConstants.PARAM_KEY_PDS_CONFIG_TEMPLATE_META_DATA_LIST); + + assertThat(jsonFromConfigSupport).isNotNull().isEqualTo(expectedJson).hasSizeGreaterThan(10); + + } + + @Test + void createJobParametersToSendToPDS_templateDataService_is_called_twice_in_correct_order_and_arguments() throws Exception { + /* prepare */ + List templateDefinitions = mock(); + when(sechubExecutionContext.getTemplateDefinitions()).thenReturn(templateDefinitions); + + List templateMetaDataServiceResult = createTemplateMetaDataServiceExampleResult(); + + when(templateMetaDataService.createTemplateMetaData(eq(templateDefinitions), anyString(), any(ScanType.class), eq(sechubConfiguration))) + .thenReturn(templateMetaDataServiceResult); + // next line does ensure that it is called with the former result. It also + // breaks further processing + doThrow(new TestCanaryException()).when(templateMetaDataService).ensureTemplateAssetFilesAreAvailableInStorage(templateMetaDataServiceResult); + + /* execute + test */ + assertThatThrownBy(() -> supportToTest.createJobParametersToSendToPDS(sechubExecutionContext)).isInstanceOf(TestCanaryException.class); + + } + + private List createTemplateMetaDataServiceExampleResult() { + List templateMetaDataServiceResult = new ArrayList<>(); + PDSTemplateMetaData pdsTemplateMetaData1 = new PDSTemplateMetaData(); + PDSAssetData assetData1 = new PDSAssetData(); + assetData1.setAssetId("asset-id-1"); + assetData1.setChecksum("checksum1"); + assetData1.setFileName("file1"); + pdsTemplateMetaData1.setAssetData(assetData1); + templateMetaDataServiceResult.add(pdsTemplateMetaData1); + return templateMetaDataServiceResult; + } + private void mockSecHubMappingId2InDatabase() { String mappingId = SECHUB_MAPPING_ID_2; diff --git a/sechub-scan-product-pds/src/test/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSTemplateMetaDataServiceTest.java b/sechub-scan-product-pds/src/test/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSTemplateMetaDataServiceTest.java new file mode 100644 index 0000000000..1d5d9ad1d1 --- /dev/null +++ b/sechub-scan-product-pds/src/test/java/com/mercedesbenz/sechub/domain/scan/product/pds/PDSTemplateMetaDataServiceTest.java @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.product.pds; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.mercedesbenz.sechub.commons.core.ConfigurationFailureException; +import com.mercedesbenz.sechub.commons.model.ScanType; +import com.mercedesbenz.sechub.commons.model.SecHubConfigurationModel; +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition; +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition.TemplateVariable; +import com.mercedesbenz.sechub.commons.model.template.TemplateType; +import com.mercedesbenz.sechub.commons.pds.data.PDSTemplateMetaData; +import com.mercedesbenz.sechub.commons.pds.data.PDSTemplateMetaData.PDSAssetData; +import com.mercedesbenz.sechub.domain.scan.asset.AssetDetailData; +import com.mercedesbenz.sechub.domain.scan.asset.AssetFileData; +import com.mercedesbenz.sechub.domain.scan.asset.AssetService; +import com.mercedesbenz.sechub.sharedkernel.error.NotFoundException; + +class PDSTemplateMetaDataServiceTest { + private PDSTemplateMetaDataService serviceToTest; + private AssetService assetService; + private RelevantScanTemplateDefinitionFilter filter; + + @BeforeEach + void beforeEach() { + + filter = mock(); + assetService = mock(); + + serviceToTest = new PDSTemplateMetaDataService(); + serviceToTest.filter = filter; + serviceToTest.assetService = assetService; + } + + @Test + void createTemplateMetaData_returns_pds_template_meta_data_when_asset_is_available() throws Exception { + + /* prepare */ + ScanType exampleScanType = ScanType.WEB_SCAN; + TemplateType exampleTemplateType = TemplateType.WEBSCAN_LOGIN; + String exampleTemplateId1 = "template_id_1"; + String exampleChecksum1 = "checksum1"; + String exampleAssetId1 = "asset1"; + + SecHubConfigurationModel configuration = mock(); + + // prepare template data + List givenDefinitions = mock(); + List filteredDefinitions = new ArrayList<>(); + TemplateVariable variable1 = new TemplateVariable(); + variable1.setName("var1"); + + TemplateDefinition templateDefinition1 = new TemplateDefinition(); + templateDefinition1.setAssetId(exampleAssetId1); + templateDefinition1.setId(exampleTemplateId1); + templateDefinition1.setType(exampleTemplateType); + + templateDefinition1.getVariables().add(variable1); + filteredDefinitions.add(templateDefinition1); + + when(filter.filter(givenDefinitions, exampleScanType, configuration)).thenReturn(filteredDefinitions); + + // prepare asset data + AssetFileData assetFile1a = new AssetFileData(); + assetFile1a.setChecksum("other"); + assetFile1a.setFileName("other_product_id.zip"); + AssetDetailData assetDetailData1 = mock(); + + AssetFileData assetFile1b = new AssetFileData(); + assetFile1b.setChecksum(exampleChecksum1); + assetFile1b.setFileName("test_product_id.zip"); + + List assetfiles = new ArrayList<>(); + assetfiles.add(assetFile1a); + assetfiles.add(assetFile1b); + + when(assetDetailData1.getFiles()).thenReturn(assetfiles); + when(assetService.fetchAssetDetails(exampleAssetId1)).thenReturn(assetDetailData1); + + /* execute */ + List result = serviceToTest.createTemplateMetaData(givenDefinitions, "test_product_id", exampleScanType, configuration); + + /* test */ + PDSTemplateMetaData expectedTemplateMetaData1 = new PDSTemplateMetaData(); + expectedTemplateMetaData1.setTemplateId(exampleTemplateId1); + expectedTemplateMetaData1.setTemplateType(exampleTemplateType); + + PDSAssetData assetData1 = new PDSAssetData(); + assetData1.setAssetId(exampleAssetId1); + assetData1.setChecksum(exampleChecksum1); + assetData1.setFileName("test_product_id.zip"); + + expectedTemplateMetaData1.setAssetData(assetData1); + + assertThat(result).isNotNull().contains(expectedTemplateMetaData1).hasSize(1); + } + + @Test + void createTemplateMetaData_returns_empty_pds_template_meta_data_when_definition_list_is_empty() throws Exception { + + /* prepare */ + ScanType exampleScanType = ScanType.WEB_SCAN; + + SecHubConfigurationModel configuration = mock(); + + // prepare template data + List givenDefinitions = mock(); + List filteredDefinitions = new ArrayList<>(); + + when(filter.filter(givenDefinitions, exampleScanType, configuration)).thenReturn(filteredDefinitions); + + /* execute */ + List result = serviceToTest.createTemplateMetaData(givenDefinitions, "test_product_id", exampleScanType, configuration); + + /* test */ + assertThat(result).isNotNull().isEmpty(); + } + + @Test + void createTemplateMetaData_throws_exception_when_asset_not_found() throws Exception { + + /* prepare */ + ScanType exampleScanType = ScanType.WEB_SCAN; + TemplateType exampleTemplateType = TemplateType.WEBSCAN_LOGIN; + String exampleTemplateId1 = "template_id_1"; + String exampleAssetId1 = "asset1"; + + SecHubConfigurationModel configuration = mock(); + + // prepare template data + List givenDefinitions = mock(); + List filteredDefinitions = new ArrayList<>(); + TemplateVariable variable1 = new TemplateVariable(); + variable1.setName("var1"); + + TemplateDefinition templateDefinition1 = new TemplateDefinition(); + templateDefinition1.setAssetId(exampleAssetId1); + templateDefinition1.setId(exampleTemplateId1); + templateDefinition1.setType(exampleTemplateType); + + templateDefinition1.getVariables().add(variable1); + filteredDefinitions.add(templateDefinition1); + + when(filter.filter(givenDefinitions, exampleScanType, configuration)).thenReturn(filteredDefinitions); + + when(assetService.fetchAssetDetails(exampleAssetId1)).thenThrow(NotFoundException.class); + + /* execute + test */ + assertThatThrownBy(() -> serviceToTest.createTemplateMetaData(givenDefinitions, "test_product_id", exampleScanType, configuration)) + .isInstanceOf(ConfigurationFailureException.class).hasMessageContaining(exampleAssetId1 + " does not exist"); + } + + @Test + void createTemplateMetaData_throws_exception_when_asset_product_file_not_found() throws Exception { + + /* prepare */ + ScanType exampleScanType = ScanType.WEB_SCAN; + TemplateType exampleTemplateType = TemplateType.WEBSCAN_LOGIN; + String exampleTemplateId1 = "template_id_1"; + String exampleAssetId1 = "asset1"; + + SecHubConfigurationModel configuration = mock(); + + // prepare template data + List givenDefinitions = mock(); + List filteredDefinitions = new ArrayList<>(); + TemplateVariable variable1 = new TemplateVariable(); + variable1.setName("var1"); + + TemplateDefinition templateDefinition1 = new TemplateDefinition(); + templateDefinition1.setAssetId(exampleAssetId1); + templateDefinition1.setId(exampleTemplateId1); + templateDefinition1.setType(exampleTemplateType); + + templateDefinition1.getVariables().add(variable1); + filteredDefinitions.add(templateDefinition1); + + when(filter.filter(givenDefinitions, exampleScanType, configuration)).thenReturn(filteredDefinitions); + // prepare asset data + AssetFileData assetFile1a = new AssetFileData(); + assetFile1a.setChecksum("other"); + assetFile1a.setFileName("other_product_id.zip"); + AssetDetailData assetDetailData1 = mock(); + + List assetfiles = new ArrayList<>(); + assetfiles.add(assetFile1a); + + when(assetDetailData1.getFiles()).thenReturn(assetfiles); + when(assetService.fetchAssetDetails(exampleAssetId1)).thenReturn(assetDetailData1); + + /* execute + test */ + assertThatThrownBy(() -> serviceToTest.createTemplateMetaData(givenDefinitions, "test_product_id", exampleScanType, configuration)) + .isInstanceOf(ConfigurationFailureException.class).hasMessageContaining("does not contain file 'test_product_id.zip'"); + ; + } + + @Test + void ensureTemplateAssetFilesAreAvailableInStorage_calls_asste_service_for_each_metadata_assetfile() throws Exception { + + /* prepare */ + PDSAssetData assetData1 = new PDSAssetData(); + assetData1.setAssetId("asset1"); + assetData1.setFileName("file1.txt"); + + PDSTemplateMetaData templateMetaData1 = new PDSTemplateMetaData(); + templateMetaData1.setAssetData(assetData1); + + PDSAssetData assetData2 = new PDSAssetData(); + assetData2.setAssetId("asset2"); + assetData2.setFileName("file2.txt"); + + PDSTemplateMetaData templateMetaData2 = new PDSTemplateMetaData(); + templateMetaData2.setAssetData(assetData2); + + List metaDataList = new ArrayList<>(); + metaDataList.add(templateMetaData1); + metaDataList.add(templateMetaData2); + + /* execute */ + serviceToTest.ensureTemplateAssetFilesAreAvailableInStorage(metaDataList); + + /* test */ + verify(assetService).ensureAssetFileInStorageAvailableAndHasSameChecksumAsInDatabase("file1.txt", "asset1"); + verify(assetService).ensureAssetFileInStorageAvailableAndHasSameChecksumAsInDatabase("file2.txt", "asset2"); + } + +} diff --git a/sechub-scan-product-pds/src/test/java/com/mercedesbenz/sechub/domain/scan/product/pds/RelevantScanTemplateDefinitionFilterTest.java b/sechub-scan-product-pds/src/test/java/com/mercedesbenz/sechub/domain/scan/product/pds/RelevantScanTemplateDefinitionFilterTest.java new file mode 100644 index 0000000000..365d9ab202 --- /dev/null +++ b/sechub-scan-product-pds/src/test/java/com/mercedesbenz/sechub/domain/scan/product/pds/RelevantScanTemplateDefinitionFilterTest.java @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.product.pds; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.EnumSource.Mode; +import org.junit.jupiter.params.provider.NullSource; + +import com.mercedesbenz.sechub.commons.model.ScanType; +import com.mercedesbenz.sechub.commons.model.SecHubConfigurationModel; +import com.mercedesbenz.sechub.commons.model.template.TemplateData; +import com.mercedesbenz.sechub.commons.model.template.TemplateDataResolver; +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition; +import com.mercedesbenz.sechub.commons.model.template.TemplateType; + +class RelevantScanTemplateDefinitionFilterTest { + private RelevantScanTemplateDefinitionFilter filterToTest; + private SecHubConfigurationModel configuration; + private TemplateDataResolver templateDataResolver; + private TemplateDefinition defininition1WebScanLogin; + private TemplateDefinition defininition2NoTemplateType; + private List templateDefinitions; + + @BeforeEach + void beforeEach() { + templateDataResolver = mock(); + configuration = mock(); + + filterToTest = new RelevantScanTemplateDefinitionFilter(); + filterToTest.templateDataResolver = templateDataResolver; + + defininition1WebScanLogin = new TemplateDefinition(); + defininition1WebScanLogin.setType(TemplateType.WEBSCAN_LOGIN); + + defininition2NoTemplateType = new TemplateDefinition(); + defininition2NoTemplateType.setType(null); + + templateDefinitions = new ArrayList<>(2); + templateDefinitions.add(defininition1WebScanLogin); + templateDefinitions.add(defininition2NoTemplateType); + + } + + @Test + void webscan_login_definition_inside_result_when_resolver_finds_template_data_and_scan_type_is_web_scan() { + /* prepare */ + TemplateData templateData = mock(); + + when(templateDataResolver.resolveTemplateData(TemplateType.WEBSCAN_LOGIN, configuration)).thenReturn(templateData); + + /* execute */ + List result = filterToTest.filter(templateDefinitions, ScanType.WEB_SCAN, configuration); + + /* test */ + assertThat(result).contains(defininition1WebScanLogin).doesNotContain(defininition2NoTemplateType).hasSize(1); + } + + @ParameterizedTest + @EnumSource(value = ScanType.class, mode = Mode.EXCLUDE, names = "WEB_SCAN") + @NullSource + void webscan_login_definition_not_inside_result_when_resolver_finds_template_data_but_scan_type_is_not_web_scan(ScanType scanType) { + /* prepare */ + TemplateData templateData = mock(); + + when(templateDataResolver.resolveTemplateData(TemplateType.WEBSCAN_LOGIN, configuration)).thenReturn(templateData); + + /* execute */ + List result = filterToTest.filter(templateDefinitions, scanType, configuration); + + /* test */ + assertThat(result).isEmpty(); + } + + @ParameterizedTest + @EnumSource(value = ScanType.class) + @NullSource + void no_login_definition_inside_result_when_resolver_does_not_find_template_data(ScanType scanType) { + /* prepare */ + when(templateDataResolver.resolveTemplateData(TemplateType.WEBSCAN_LOGIN, configuration)).thenReturn(null); + + /* execute */ + List result = filterToTest.filter(templateDefinitions, scanType, configuration); + + /* test */ + assertThat(result).isEmpty(); + } + +} diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/adapter/DefaultExecutorConfigSupport.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/adapter/DefaultExecutorConfigSupport.java index 63abcbeae6..ec6eac4871 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/adapter/DefaultExecutorConfigSupport.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/adapter/DefaultExecutorConfigSupport.java @@ -14,6 +14,7 @@ import com.mercedesbenz.sechub.commons.mapping.NamePatternIdProvider; import com.mercedesbenz.sechub.commons.mapping.NamePatternIdProviderFactory; import com.mercedesbenz.sechub.commons.model.SecHubRuntimeException; +import com.mercedesbenz.sechub.domain.scan.product.ProductExecutorContext; import com.mercedesbenz.sechub.domain.scan.product.config.ProductExecutorConfig; import com.mercedesbenz.sechub.domain.scan.product.config.ProductExecutorConfigSetupCredentials; import com.mercedesbenz.sechub.domain.scan.product.config.ProductExecutorConfigSetupJobParameter; @@ -41,12 +42,13 @@ public class DefaultExecutorConfigSupport { NamePatternIdProviderFactory providerFactory; - public DefaultExecutorConfigSupport(ProductExecutorConfig config, SystemEnvironmentVariableSupport variableSupport, + public DefaultExecutorConfigSupport(ProductExecutorContext context, SystemEnvironmentVariableSupport variableSupport, Validation validation) { - notNull(config, "config may not be null!"); + notNull(context, "context may not be null!"); + notNull(context.getExecutorConfig(), "executor config may not be null!"); notNull(variableSupport, "variableSupport may not be null!"); - this.config = config; + this.config = context.getExecutorConfig(); this.variableSupport = variableSupport; providerFactory = new NamePatternIdProviderFactory(); diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/DefaultAdapterConfigurationStrategy.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/DefaultAdapterConfigurationStrategy.java index 128cd891bf..3f97199e3f 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/DefaultAdapterConfigurationStrategy.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/DefaultAdapterConfigurationStrategy.java @@ -5,6 +5,7 @@ import com.mercedesbenz.sechub.adapter.AdapterConfigBuilder; import com.mercedesbenz.sechub.adapter.AdapterConfigurationStrategy; import com.mercedesbenz.sechub.adapter.DefaultExecutorConfigSupport; +import com.mercedesbenz.sechub.commons.core.ConfigurationFailureException; import com.mercedesbenz.sechub.commons.model.ScanType; import com.mercedesbenz.sechub.domain.scan.product.ProductExecutorData; @@ -46,7 +47,7 @@ public DefaultAdapterConfigurationStrategy(ProductExecutorData data, DefaultExec } @Override - public void configure(B configBuilder) { + public void configure(B configBuilder) throws ConfigurationFailureException { /* @formatter:off */ SecHubExecutionContext context = data.getSechubExecutionContext(); String projectId = context.getConfiguration().getProjectId(); diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/IntegrationTestScanRestController.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/IntegrationTestScanRestController.java index 2568951e77..d949f38dd4 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/IntegrationTestScanRestController.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/IntegrationTestScanRestController.java @@ -37,6 +37,8 @@ import com.mercedesbenz.sechub.domain.scan.product.config.ProductExecutionProfileRepository; import com.mercedesbenz.sechub.domain.scan.product.config.ProductExecutorConfigInfo; import com.mercedesbenz.sechub.domain.scan.product.config.WithoutProductExecutorConfigInfo; +import com.mercedesbenz.sechub.domain.scan.project.ScanProjectConfig; +import com.mercedesbenz.sechub.domain.scan.project.ScanProjectConfigRepository; import com.mercedesbenz.sechub.domain.scan.report.ScanReportCountService; import com.mercedesbenz.sechub.sharedkernel.ProductIdentifier; import com.mercedesbenz.sechub.sharedkernel.Profiles; @@ -92,6 +94,9 @@ public class IntegrationTestScanRestController { @Autowired private ScanConfigService scanConfigService; + @Autowired + private ScanProjectConfigRepository scanProjectConfigRepository; + @RequestMapping(path = APIConstants.API_ANONYMOUS + "integrationtest/autocleanup/inspection/scan/days", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE }) public long fetchScheduleAutoCleanupConfiguredDays() { @@ -262,4 +267,9 @@ public List getPDSJobUUIDSForSecHubJOob(@PathVariable("jobUUID") UUID sech return list; } + @RequestMapping(path = APIConstants.API_ANONYMOUS + "integrationtest/project-scanconfig/{projectId}", method = RequestMethod.GET) + public List fetchScanConfigValue(@PathVariable("projectId") String projectId) { + return scanProjectConfigRepository.findAllForProject(projectId); + } + } diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ProjectDataDeleteService.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ProjectDataDeleteService.java index 2580f42855..fc4bb67041 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ProjectDataDeleteService.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ProjectDataDeleteService.java @@ -59,6 +59,10 @@ public void deleteAllDataForProject(String projectId) { productResultRepository.deleteAllResultsForProject(projectId); scanReportRepository.deleteAllReportsForProject(projectId); scanLogRepository.deleteAllLogDataForProject(projectId); + /* + * next line deletes any project related configuration - this includes template + * assignment + */ scanProjectConfigRepository.deleteAllConfigurationsForProject(projectId); profileRepository.deleteAllProfileRelationsToProject(projectId); diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ScanJobExecutionRunnable.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ScanJobExecutionRunnable.java index c102279096..8595244d30 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ScanJobExecutionRunnable.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ScanJobExecutionRunnable.java @@ -14,6 +14,7 @@ import com.mercedesbenz.sechub.domain.scan.product.WebScanProductExecutionService; import com.mercedesbenz.sechub.sharedkernel.LogConstants; import com.mercedesbenz.sechub.sharedkernel.Step; +import com.mercedesbenz.sechub.sharedkernel.usecases.job.UseCaseSchedulerStartsJob; import com.mercedesbenz.sechub.sharedkernel.usecases.other.UseCaseSystemSuspendsJobsWhenSigTermReceived; /** @@ -36,6 +37,13 @@ public ScanJobRunnableData getRunnableData() { } @Override + @UseCaseSchedulerStartsJob(@Step(number = 3, name = "Runnable calls execution services", + + description = """ + The job execution runnable creates the execution context and calls dedicated execution services + (preparation, analytics, product execution, storage and reporting) synchronously for the job. + It is also responsible for cancelation and supsension of jobs. + """)) public void run() { /* runs in own thread so we set job uuid to MDC here ! */ try { diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ScanMessageHandler.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ScanMessageHandler.java index e2cbade215..0f7143a77c 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ScanMessageHandler.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ScanMessageHandler.java @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.domain.scan; +import java.util.Set; import java.util.UUID; import org.slf4j.Logger; @@ -16,6 +17,7 @@ import com.mercedesbenz.sechub.domain.scan.config.UpdateScanMappingConfigurationService; import com.mercedesbenz.sechub.domain.scan.product.ProductResultService; import com.mercedesbenz.sechub.domain.scan.project.ScanProjectConfigAccessLevelService; +import com.mercedesbenz.sechub.domain.scan.template.TemplateService; import com.mercedesbenz.sechub.sharedkernel.Step; import com.mercedesbenz.sechub.sharedkernel.mapping.MappingIdentifier; import com.mercedesbenz.sechub.sharedkernel.mapping.MappingIdentifier.MappingType; @@ -33,6 +35,8 @@ import com.mercedesbenz.sechub.sharedkernel.messaging.SynchronMessageHandler; import com.mercedesbenz.sechub.sharedkernel.messaging.UserMessage; import com.mercedesbenz.sechub.sharedkernel.project.ProjectAccessLevel; +import com.mercedesbenz.sechub.sharedkernel.template.SecHubProjectTemplateData; +import com.mercedesbenz.sechub.sharedkernel.template.SecHubProjectToTemplate; import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdmiUpdatesMappingConfiguration; import com.mercedesbenz.sechub.sharedkernel.usecases.admin.project.UseCaseAdminChangesProjectAccessLevel; @@ -68,6 +72,9 @@ public class ScanMessageHandler implements AsynchronMessageHandler, SynchronMess @Autowired ScanConfigService configService; + @Autowired + TemplateService templateService; + @Override public void receiveAsyncMessage(DomainMessage request) { MessageID messageId = request.getMessageId(); @@ -114,12 +121,47 @@ public DomainMessageSynchronousResult receiveSynchronMessage(DomainMessage reque case REQUEST_PURGE_JOB_RESULTS: return handleJobRestartHardRequested(request); - + case REQUEST_ASSIGN_TEMPLATE_TO_PROJECT: + return handleAssignTemplateToProjectRequest(request); + case REQUEST_UNASSIGN_TEMPLATE_FROM_PROJECT: + return handleUnassignTemplateFromProjectRequest(request); default: throw new IllegalStateException("unhandled message id:" + messageId); } } + @IsRecevingSyncMessage(MessageID.REQUEST_ASSIGN_TEMPLATE_TO_PROJECT) + private DomainMessageSynchronousResult handleAssignTemplateToProjectRequest(DomainMessage request) { + SecHubProjectToTemplate projectToTemplate = request.get(MessageDataKeys.PROJECT_TO_TEMPLATE); + String templateId = projectToTemplate.getTemplateId(); + String projectId = projectToTemplate.getProjectId(); + + try { + templateService.assignTemplateToProject(templateId, projectId); + Set templateIds = templateService.fetchAssignedTemplateIdsForProject(projectId); + return templateAssignmentDone(projectId, templateIds); + } catch (Exception e) { + return templateAssignmentFailed(e); + } + + } + + @IsRecevingSyncMessage(MessageID.REQUEST_UNASSIGN_TEMPLATE_FROM_PROJECT) + private DomainMessageSynchronousResult handleUnassignTemplateFromProjectRequest(DomainMessage request) { + SecHubProjectToTemplate projectToTemplate = request.get(MessageDataKeys.PROJECT_TO_TEMPLATE); + String templateId = projectToTemplate.getTemplateId(); + String projectId = projectToTemplate.getProjectId(); + + try { + templateService.unassignTemplateFromProject(templateId, projectId); + Set templateIds = templateService.fetchAssignedTemplateIdsForProject(projectId); + return templateUnassignmentDone(projectId, templateIds); + } catch (Exception e) { + return templateUnassignmentFailed(e); + } + + } + @IsRecevingSyncMessage(MessageID.REQUEST_PURGE_JOB_RESULTS) private DomainMessageSynchronousResult handleJobRestartHardRequested(DomainMessage request) { UUID jobUUID = request.get(MessageDataKeys.SECHUB_JOB_UUID); @@ -147,6 +189,40 @@ private DomainMessageSynchronousResult purgeDone(UUID jobUUID) { return result; } + @IsSendingSyncMessageAnswer(value = MessageID.RESULT_ASSIGN_TEMPLATE_TO_PROJECT, answeringTo = MessageID.REQUEST_ASSIGN_TEMPLATE_TO_PROJECT, branchName = "success") + private DomainMessageSynchronousResult templateAssignmentDone(String projectId, Set assignedTemplates) { + DomainMessageSynchronousResult result = new DomainMessageSynchronousResult(MessageID.RESULT_ASSIGN_TEMPLATE_TO_PROJECT); + SecHubProjectTemplateData templates = new SecHubProjectTemplateData(); + templates.setProjectId(projectId); + templates.getTemplateIds().addAll(assignedTemplates); + + result.set(MessageDataKeys.PROJECT_TEMPLATES, templates); + return result; + } + + @IsSendingSyncMessageAnswer(value = MessageID.RESULT_ASSIGN_TEMPLATE_TO_PROJECT, answeringTo = MessageID.REQUEST_ASSIGN_TEMPLATE_TO_PROJECT, branchName = "failed") + private DomainMessageSynchronousResult templateAssignmentFailed(Exception failure) { + DomainMessageSynchronousResult result = new DomainMessageSynchronousResult(MessageID.RESULT_ASSIGN_TEMPLATE_TO_PROJECT, failure); + return result; + } + + @IsSendingSyncMessageAnswer(value = MessageID.RESULT_UNASSIGN_TEMPLATE_FROM_PROJECT, answeringTo = MessageID.REQUEST_UNASSIGN_TEMPLATE_FROM_PROJECT, branchName = "success") + private DomainMessageSynchronousResult templateUnassignmentDone(String projectId, Set assignedTemplates) { + DomainMessageSynchronousResult result = new DomainMessageSynchronousResult(MessageID.RESULT_UNASSIGN_TEMPLATE_FROM_PROJECT); + SecHubProjectTemplateData templates = new SecHubProjectTemplateData(); + templates.setProjectId(projectId); + templates.getTemplateIds().addAll(assignedTemplates); + + result.set(MessageDataKeys.PROJECT_TEMPLATES, templates); + return result; + } + + @IsSendingSyncMessageAnswer(value = MessageID.RESULT_UNASSIGN_TEMPLATE_FROM_PROJECT, answeringTo = MessageID.REQUEST_UNASSIGN_TEMPLATE_FROM_PROJECT, branchName = "failed") + private DomainMessageSynchronousResult templateUnassignmentFailed(Exception failure) { + DomainMessageSynchronousResult result = new DomainMessageSynchronousResult(MessageID.RESULT_UNASSIGN_TEMPLATE_FROM_PROJECT, failure); + return result; + } + @IsReceivingAsyncMessage(MessageID.MAPPING_CONFIGURATION_CHANGED) @UseCaseAdmiUpdatesMappingConfiguration(@Step(number = 3, name = "Event handler", description = "Receives mapping configuration change event")) private void handleMappingConfigurationChanged(DomainMessage request) { diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ScanService.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ScanService.java index 6ad21e23b4..98f2563fef 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ScanService.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/ScanService.java @@ -26,6 +26,7 @@ import com.mercedesbenz.sechub.domain.scan.report.CreateScanReportService; import com.mercedesbenz.sechub.domain.scan.report.ScanReport; import com.mercedesbenz.sechub.domain.scan.report.ScanReportException; +import com.mercedesbenz.sechub.domain.scan.template.TemplateService; import com.mercedesbenz.sechub.sharedkernel.MustBeDocumented; import com.mercedesbenz.sechub.sharedkernel.ProgressStateFetcher; import com.mercedesbenz.sechub.sharedkernel.Step; @@ -80,6 +81,9 @@ public class ScanService implements SynchronMessageHandler { @Autowired ScanProgressStateFetcherFactory monitorFactory; + @Autowired + TemplateService templateService; + @MustBeDocumented("Define delay in milliseconds, for before next job cancellation check will be executed.") @Value("${sechub.config.check.canceljob.delay:" + DEFAULT_CHECK_CANCELJOB_DELAY_MILLIS + "}") private int millisecondsToWaitBeforeCancelCheck = DEFAULT_CHECK_CANCELJOB_DELAY_MILLIS; @@ -195,7 +199,7 @@ private void cleanupStorage(SecHubExecutionContext context) { } - private SecHubExecutionContext createExecutionContext(DomainMessage message) throws JSONConverterException { + SecHubExecutionContext createExecutionContext(DomainMessage message) throws JSONConverterException { UUID executionUUID = message.get(SECHUB_EXECUTION_UUID); UUID sechubJobUUID = message.get(SECHUB_JOB_UUID); @@ -206,7 +210,6 @@ private SecHubExecutionContext createExecutionContext(DomainMessage message) thr throw new IllegalStateException("SecHubConfiguration not found in message - so cannot execute!"); } SecHubExecutionContext executionContext = new SecHubExecutionContext(sechubJobUUID, configuration, executedBy, executionUUID); - buildOptions(executionContext); return executionContext; @@ -224,6 +227,10 @@ private void buildOptions(SecHubExecutionContext executionContext) { ScanProjectMockDataConfiguration mockDataConfig = ScanProjectMockDataConfiguration.fromString(data); executionContext.putData(ScanKey.PROJECT_MOCKDATA_CONFIGURATION, mockDataConfig); } + + /* append template definitions */ + executionContext.getTemplateDefinitions().addAll(templateService.fetchAllTemplateDefinitionsForProject(projectId)); + } @Override diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/SecHubExecutionContext.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/SecHubExecutionContext.java index 43f2b7621c..f93e6969d2 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/SecHubExecutionContext.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/SecHubExecutionContext.java @@ -2,13 +2,16 @@ package com.mercedesbenz.sechub.domain.scan; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.UUID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition; import com.mercedesbenz.sechub.domain.scan.product.ProductExecutor; import com.mercedesbenz.sechub.domain.scan.product.ProductExecutorData; import com.mercedesbenz.sechub.sharedkernel.TypedKey; @@ -50,6 +53,8 @@ public class SecHubExecutionContext { private boolean suspended; + private List templateDefinitions = new ArrayList<>(); + public SecHubExecutionContext(UUID sechubJobUUID, SecHubConfiguration configuration, String executedBy, UUID executionUUID) { this(sechubJobUUID, configuration, executedBy, executionUUID, null); } @@ -191,4 +196,8 @@ public boolean isSuspended() { return suspended; } + public List getTemplateDefinitions() { + return templateDefinitions; + } + } diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/asset/AssetDetailData.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/asset/AssetDetailData.java new file mode 100644 index 0000000000..79942f30f8 --- /dev/null +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/asset/AssetDetailData.java @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.asset; + +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.mercedesbenz.sechub.commons.model.JSONable; + +@JsonIgnoreProperties(ignoreUnknown = true) // we do ignore to avoid problems from wrong configured values! +public class AssetDetailData implements JSONable { + + private String assetId; + + private List files = new ArrayList<>(); + + public void setAssetId(String assetid) { + this.assetId = assetid; + } + + public String getAssetId() { + return assetId; + } + + public List getFiles() { + return files; + } + + @Override + public Class getJSONTargetClass() { + return AssetDetailData.class; + } +} diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/asset/AssetFile.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/asset/AssetFile.java new file mode 100644 index 0000000000..9d912b89e6 --- /dev/null +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/asset/AssetFile.java @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.asset; + +import java.io.Serializable; +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EmbeddedId; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import jakarta.persistence.Version; + +/** + * Represents a template + * + * @author Albert Tregnaghi + * + */ +@Entity +@Table(name = AssetFile.TABLE_NAME) +public class AssetFile { + + /* +-----------------------------------------------------------------------+ */ + /* +............................ SQL ......................................+ */ + /* +-----------------------------------------------------------------------+ */ + public static final String TABLE_NAME = "SCAN_ASSET_FILE"; + + public static final String COLUMN_ASSET_ID = "ASSET_ID"; + public static final String COLUMN_FILE_NAME = "FILE_NAME"; + public static final String COLUMN_DATA = "DATA"; + public static final String COLUMN_CHECKSUM = "CHECKSUM"; + + /* +-----------------------------------------------------------------------+ */ + /* +............................ JPQL .....................................+ */ + /* +-----------------------------------------------------------------------+ */ + public static final String CLASS_NAME = "AssetFile"; + + @EmbeddedId + AssetFileCompositeKey key; + + @Column(name = COLUMN_CHECKSUM) + String checksum; + + @Column(name = COLUMN_DATA) + byte[] data; + + @Version + @Column(name = "VERSION") + @JsonIgnore + Integer version; + + AssetFile() { + // jpa only + } + + public AssetFile(AssetFileCompositeKey key) { + this.key = key; + } + + public AssetFileCompositeKey getKey() { + return key; + } + + public void setChecksum(String definition) { + this.checksum = definition; + } + + public String getChecksum() { + return checksum; + } + + public void setData(byte[] data) { + this.data = data; + } + + public byte[] getData() { + return data; + } + + /** + * Asset id and file name or only strings. To avoid confusion in constructor + * usage, this builder was introduced. + * + * @author Albert Tregnaghi + * + */ + public static class AssetFileCompositeKeyBuilder { + + private String assetId; + private String fileName; + + public AssetFileCompositeKey build() { + + if (assetId == null) { + throw new IllegalStateException("asset id not defined!"); + } + if (fileName == null) { + throw new IllegalStateException("file name not defined!"); + } + + AssetFileCompositeKey key = new AssetFileCompositeKey(); + key.setAssetId(assetId); + key.setFileName(fileName); + + return key; + } + + public AssetFileCompositeKeyBuilder assetId(String assetId) { + this.assetId = assetId; + return this; + } + + public AssetFileCompositeKeyBuilder fileName(String fileName) { + this.fileName = fileName; + return this; + } + } + + @Embeddable + public static class AssetFileCompositeKey implements Serializable { + + public static AssetFileCompositeKeyBuilder builder() { + return new AssetFileCompositeKeyBuilder(); + } + + private static final long serialVersionUID = 8753389792382752253L; + + @Column(name = COLUMN_ASSET_ID, nullable = false) + private String assetId; + + @Column(name = COLUMN_FILE_NAME, nullable = false) + private String fileName; + + AssetFileCompositeKey() { + // jpa only + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String userId) { + this.fileName = userId; + } + + public String getAssetId() { + return assetId; + } + + public void setAssetId(String projectId) { + this.assetId = projectId; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((assetId == null) ? 0 : assetId.hashCode()); + result = prime * result + ((fileName == null) ? 0 : fileName.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AssetFileCompositeKey other = (AssetFileCompositeKey) obj; + if (assetId == null) { + if (other.assetId != null) + return false; + } else if (!assetId.equals(other.assetId)) + return false; + if (fileName == null) { + if (other.fileName != null) + return false; + } else if (!fileName.equals(other.fileName)) + return false; + return true; + } + } + + @Override + public int hashCode() { + return Objects.hash(key); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AssetFile other = (AssetFile) obj; + return Objects.equals(key, other.key); + } + + @Override + public String toString() { + return "AssetFile [" + (key != null ? "key=" + key + ", " : "") + (checksum != null ? "checksum=" + checksum + ", " : "") + + (version != null ? "version=" + version : "") + "]"; + } +} diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/asset/AssetFileData.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/asset/AssetFileData.java new file mode 100644 index 0000000000..6e18327e57 --- /dev/null +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/asset/AssetFileData.java @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.asset; + +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) // we do ignore to avoid problems from wrong configured values! +public class AssetFileData { + + private String fileName; + + private String checksum; + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public String getChecksum() { + return checksum; + } + + public void setChecksum(String checksum) { + this.checksum = checksum; + } + + @Override + public int hashCode() { + return Objects.hash(checksum, fileName); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AssetFileData other = (AssetFileData) obj; + return Objects.equals(checksum, other.checksum) && Objects.equals(fileName, other.fileName); + } + + @Override + public String toString() { + return "AssetFileInformation [" + (checksum != null ? "checksum=" + checksum + ", " : "") + (fileName != null ? "fileName=" + fileName : "") + "]"; + } + +} diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/asset/AssetFileRepository.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/asset/AssetFileRepository.java new file mode 100644 index 0000000000..b6394bac30 --- /dev/null +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/asset/AssetFileRepository.java @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.asset; + +import static com.mercedesbenz.sechub.domain.scan.asset.AssetFile.*; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import com.mercedesbenz.sechub.domain.scan.asset.AssetFile.AssetFileCompositeKey; + +public interface AssetFileRepository extends JpaRepository { + + @Query(value = "SELECT DISTINCT " + COLUMN_ASSET_ID + " FROM " + TABLE_NAME, nativeQuery = true) + List fetchAllAssetIds(); + + @Query(value = "SELECT * from " + TABLE_NAME + " WHERE " + COLUMN_ASSET_ID + "=:assetId", nativeQuery = true) + List fetchAllAssetFilesWithAssetId(@Param("assetId") String assetId); + + @Modifying + @Query(value = "DELETE from " + TABLE_NAME + " WHERE " + COLUMN_ASSET_ID + "=:assetId", nativeQuery = true) + void deleteAssetFilesHavingAssetId(@Param("assetId") String assetId); + +} diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/asset/AssetRestController.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/asset/AssetRestController.java new file mode 100644 index 0000000000..c4f0b1f83d --- /dev/null +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/asset/AssetRestController.java @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.asset; + +import static com.mercedesbenz.sechub.commons.core.CommonConstants.*; + +import java.io.IOException; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Profile; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import com.mercedesbenz.sechub.sharedkernel.Profiles; +import com.mercedesbenz.sechub.sharedkernel.Step; +import com.mercedesbenz.sechub.sharedkernel.logging.AuditLogService; +import com.mercedesbenz.sechub.sharedkernel.logging.LogSanitizer; +import com.mercedesbenz.sechub.sharedkernel.security.APIConstants; +import com.mercedesbenz.sechub.sharedkernel.security.RoleConstants; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminDeletesAssetCompletely; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminDeletesOneFileFromAsset; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminDownloadsAssetFile; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminFetchesAssetDetails; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminFetchesAssetIds; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminUploadsAssetFile; + +import jakarta.annotation.security.RolesAllowed; +import jakarta.servlet.http.HttpServletResponse; + +@RestController +@EnableAutoConfiguration +@RequestMapping(APIConstants.API_ADMINISTRATION) +@RolesAllowed({ RoleConstants.ROLE_SUPERADMIN }) +@Profile(Profiles.ADMIN_ACCESS) +public class AssetRestController { + + @Autowired + AssetService assetService; + + @Autowired + AuditLogService auditLogService; + + @Autowired + LogSanitizer logSanitizer; + + /* @formatter:off */ + @UseCaseAdminUploadsAssetFile(@Step(number = 1, next = 2, name = "REST API call to upload a file for an asset", needsRestDoc = true)) + @RequestMapping(path = "/asset/{assetId}/file", method = RequestMethod.POST) + @ResponseStatus(HttpStatus.OK) + /* @formatter:on */ + public void uploadAssetFile(@PathVariable("assetId") String assetId, @RequestPart(MULTIPART_FILE) MultipartFile file, + @RequestParam(MULTIPART_CHECKSUM) String checkSum) { + + auditLogService.log("starts upload of file:{} for asset: {}", logSanitizer.sanitize(file.getOriginalFilename(), 100), + logSanitizer.sanitize(assetId, 40)); + + assetService.uploadAssetFile(assetId, file, checkSum); + + } + + /* @formatter:off */ + @UseCaseAdminDownloadsAssetFile(@Step(number = 1, next = 2, name = "REST API call to download a file for an asset", needsRestDoc = true)) + @RequestMapping(path = "/asset/{assetId}/file/{fileName}", method = RequestMethod.GET, produces = {MediaType.APPLICATION_OCTET_STREAM_VALUE, MediaType.ALL_VALUE}) + @ResponseStatus(HttpStatus.OK) + /* @formatter:on */ + public void downloadAssetFile(@PathVariable("assetId") String assetId, @PathVariable("fileName") String fileName, HttpServletResponse response) + throws IOException { + + auditLogService.log("starts download of file:{} for asset: {}", logSanitizer.sanitize(fileName, 100), logSanitizer.sanitize(assetId, 40)); + + assetService.downloadAssetFile(assetId, fileName, response.getOutputStream()); + + } + + /* @formatter:off */ + @UseCaseAdminFetchesAssetIds(@Step(number = 1, next = 2, name = "REST API call to fetch all availbale asset ids", needsRestDoc = true)) + @RequestMapping(path = "/asset/ids", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE}) + @ResponseStatus(HttpStatus.OK) + /* @formatter:on */ + public List fetchAllAssetIds() { + return assetService.fetchAllAssetIds(); + } + + /* @formatter:off */ + @UseCaseAdminFetchesAssetDetails(@Step(number = 1, next = 2, name = "REST API call to fetch details about asset", needsRestDoc = true)) + @RequestMapping(path = "/asset/{assetId}/details", method = RequestMethod.GET, produces = {MediaType.APPLICATION_JSON_VALUE}) + @ResponseStatus(HttpStatus.OK) + /* @formatter:on */ + public AssetDetailData fetchAssetDetails(@PathVariable("assetId") String assetId) { + return assetService.fetchAssetDetails(assetId); + } + + /* @formatter:off */ + @UseCaseAdminDeletesOneFileFromAsset(@Step(number = 1, next = 2, name = "REST API call to delete an asset file", needsRestDoc = true)) + @RequestMapping(path = "/asset/{assetId}/file/{fileName}", method = RequestMethod.DELETE) + @ResponseStatus(HttpStatus.OK) + /* @formatter:on */ + public void deleteAssetFile(@PathVariable("assetId") String assetId, @PathVariable("fileName") String fileName) throws IOException { + assetService.deleteAssetFile(assetId, fileName); + } + + /* @formatter:off */ + @UseCaseAdminDeletesAssetCompletely(@Step(number = 1, next = 2, name = "REST API call to delete complete asset", needsRestDoc = true)) + @RequestMapping(path = "/asset/{assetId}", method = RequestMethod.DELETE) + @ResponseStatus(HttpStatus.OK) + /* @formatter:on */ + public void deleteAsset(@PathVariable("assetId") String assetId) throws IOException { + assetService.deleteAsset(assetId); + } +} diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/asset/AssetService.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/asset/AssetService.java new file mode 100644 index 0000000000..28965552cb --- /dev/null +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/asset/AssetService.java @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.asset; + +import static com.mercedesbenz.sechub.commons.core.CommonConstants.*; +import static com.mercedesbenz.sechub.sharedkernel.util.Assert.*; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Optional; +import java.util.Scanner; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import com.amazonaws.util.StringInputStream; +import com.mercedesbenz.sechub.commons.core.ConfigurationFailureException; +import com.mercedesbenz.sechub.commons.core.security.CheckSumSupport; +import com.mercedesbenz.sechub.commons.model.SecHubRuntimeException; +import com.mercedesbenz.sechub.domain.scan.asset.AssetFile.AssetFileCompositeKey; +import com.mercedesbenz.sechub.sharedkernel.Step; +import com.mercedesbenz.sechub.sharedkernel.error.BadRequestException; +import com.mercedesbenz.sechub.sharedkernel.error.NotAcceptableException; +import com.mercedesbenz.sechub.sharedkernel.error.NotFoundException; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminDeletesAssetCompletely; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminDeletesOneFileFromAsset; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminDownloadsAssetFile; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminFetchesAssetDetails; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminFetchesAssetIds; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminUploadsAssetFile; +import com.mercedesbenz.sechub.sharedkernel.validation.UserInputAssertion; +import com.mercedesbenz.sechub.storage.core.AssetStorage; +import com.mercedesbenz.sechub.storage.core.StorageService; + +import jakarta.servlet.ServletOutputStream; + +@Service +public class AssetService { + + private static final Logger LOG = LoggerFactory.getLogger(AssetService.class); + + private final AssetFileRepository repository; + + private final UserInputAssertion inputAssertion; + + private final CheckSumSupport checkSumSupport; + + private final StorageService storageService; + + AssetService(AssetFileRepository repository, UserInputAssertion inputAssertion, CheckSumSupport checkSumSupport, StorageService storageService) { + this.repository = repository; + this.inputAssertion = inputAssertion; + this.checkSumSupport = checkSumSupport; + this.storageService = storageService; + } + + @UseCaseAdminDeletesAssetCompletely(@Step(number = 2, name = "Services deletes all asset parts")) + @Transactional + public void deleteAsset(String assetId) throws IOException { + inputAssertion.assertIsValidAssetId(assetId); + + repository.deleteAssetFilesHavingAssetId(assetId); + storageService.createAssetStorage(assetId).deleteAll(); + } + + @UseCaseAdminDeletesOneFileFromAsset(@Step(number = 2, name = "Services deletes file from asset")) + public void deleteAssetFile(String assetId, String fileName) throws IOException { + inputAssertion.assertIsValidAssetId(assetId); + inputAssertion.assertIsValidAssetFileName(fileName); + + repository.deleteById(AssetFileCompositeKey.builder().assetId(assetId).fileName(fileName).build()); + storageService.createAssetStorage(assetId).delete(fileName); + } + + @UseCaseAdminDownloadsAssetFile(@Step(number = 2, name = "Service downloads asset file from database")) + public void downloadAssetFile(String assetId, String fileName, ServletOutputStream outputStream) throws IOException { + inputAssertion.assertIsValidAssetId(assetId); + inputAssertion.assertIsValidAssetFileName(fileName); + + notNull(outputStream, "output stream may not be null!"); + + AssetFile assetFile = assertAssetFileFromDatabase(assetId, fileName); + outputStream.write(assetFile.getData()); + + } + + /** + * Ensures file for asset exists in database and and also in storage (with same + * checksum). If the file is not inside database a {@link NotFoundException} + * will be thrown. If the file is not available in storage, or the checksum in + * storage is different than the checksum from database, the file will stored + * again in storage (with data from database) + * + * @param fileName file name + * @param assetId asset identifier + * @throws ConfigurationFailureException if there are configuration problems + * @throws NotFoundException when the asset or the file is not found + * in database + * + */ + public void ensureAssetFileInStorageAvailableAndHasSameChecksumAsInDatabase(String fileName, String assetId) throws ConfigurationFailureException { + + try (AssetStorage assetStorage = storageService.createAssetStorage(assetId)) { + AssetFile assetFile = assertAssetFileFromDatabase(assetId, fileName); + String checksumFromDatabase = assetFile.getChecksum(); + + if (assetStorage.isExisting(fileName)) { + String checksumFileName = fileName + DOT_CHECKSUM; + if (assetStorage.isExisting(checksumFileName)) { + + String checksumFromStorage = null; + try (InputStream inputStream = assetStorage.fetch(checksumFileName); Scanner scanner = new Scanner(inputStream)) { + checksumFromStorage = scanner.hasNext() ? scanner.next() : ""; + } + if (checksumFromStorage.equals(checksumFromDatabase)) { + LOG.debug("Checksum for file '{}' in asset '{}' is '{}' in database and storage. Can be kept, no recration necessary", fileName, + assetId, checksumFromStorage, checksumFromDatabase); + return; + } + LOG.warn( + "Checksum for file '{}' in asset '{}' was '{}' instead of expected value from database '{}'. Will recreated file and checksum in storage.", + fileName, assetId, checksumFromStorage, checksumFromDatabase); + } else { + LOG.warn("Asset storage for file '{}' in asset '{}' did exist, but checksum did not exist. Will recreated file and checksum in storage.", + fileName, assetId); + } + } else { + LOG.info("Asset storage for file '{}' in asset '{}' does not exist and must be created.", fileName, assetId); + } + storeStream(fileName, checksumFromDatabase, assetStorage, assetFile.getData().length, new ByteArrayInputStream(assetFile.getData())); + + } catch (NotFoundException | IOException e) { + throw new ConfigurationFailureException("Was not able to ensure file " + fileName + " in asset " + assetId, e); + } + + } + + @UseCaseAdminFetchesAssetIds(@Step(number = 2, name = "Service fetches all asset ids from database")) + public List fetchAllAssetIds() { + return repository.fetchAllAssetIds(); + } + + /** + * Fetches asset details (from database) + * + * @param assetId asset identifier + * @return detail data + * @throws NotFoundException when no asset exists for given identifier + */ + @UseCaseAdminFetchesAssetDetails(@Step(number = 2, name = "Service fetches asset details for given asset id")) + public AssetDetailData fetchAssetDetails(String assetId) { + inputAssertion.assertIsValidAssetId(assetId); + + List assetFiles = repository.fetchAllAssetFilesWithAssetId(assetId); + if (assetFiles.isEmpty()) { + throw new NotFoundException("No asset data available for asset id:" + assetId); + } + + AssetDetailData data = new AssetDetailData(); + data.setAssetId(assetId); + for (AssetFile assetFile : assetFiles) { + AssetFileData information = new AssetFileData(); + information.setFileName(assetFile.getKey().getFileName()); + information.setChecksum(assetFile.getChecksum()); + data.getFiles().add(information); + } + + return data; + } + + @UseCaseAdminUploadsAssetFile(@Step(number = 2, name = "Service tries to upload file for asset", description = "Uploaded file will be stored in database and in storage")) + public void uploadAssetFile(String assetId, MultipartFile multipartFile, String checkSum) { + inputAssertion.assertIsValidAssetId(assetId); + + inputAssertion.assertIsValidSha256Checksum(checkSum); + + String fileName = assertAssetFile(multipartFile); + + handleChecksumValidation(fileName, multipartFile, checkSum, assetId); + + try { + /* now store */ + byte[] bytes = multipartFile.getBytes(); + persistFileAndChecksumInDatabase(fileName, bytes, checkSum, assetId); + + ensureAssetFileInStorageAvailableAndHasSameChecksumAsInDatabase(fileName, assetId); + + LOG.info("Successfully uploaded file '{}' for asset '{}'", fileName, assetId); + + } catch (IOException e) { + throw new SecHubRuntimeException("Was not able to upload file '" + fileName + "' for asset '" + assetId + "'", e); + } catch (ConfigurationFailureException e) { + throw new IllegalStateException("A configuration failure should not happen at this point!", e); + } + } + + private String assertAssetFile(MultipartFile file) { + notNull(file, "file may not be null!"); + String fileName = file.getOriginalFilename(); + + inputAssertion.assertIsValidAssetFileName(fileName); + + long fileSize = file.getSize(); + + if (fileSize <= 0) { + throw new BadRequestException("Uploaded asset file may not be empty!"); + } + return fileName; + } + + private AssetFile assertAssetFileFromDatabase(String assetId, String fileName) { + AssetFileCompositeKey key = AssetFileCompositeKey.builder().assetId(assetId).fileName(fileName).build(); + Optional result = repository.findById(key); + if (result.isEmpty()) { + throw new NotFoundException("For asset:" + assetId + " no file with name:" + fileName + " exists!"); + } + AssetFile assetFile = result.get(); + return assetFile; + } + + private void assertCheckSumCorrect(String checkSum, InputStream inputStream) { + if (!checkSumSupport.hasCorrectSha256Checksum(checkSum, inputStream)) { + LOG.error("Uploaded file has incorrect sha256 checksum! Something must have happened during the upload."); + throw new NotAcceptableException("Sourcecode checksum check failed"); + } + } + + private String createFileNameForChecksum(String fileName) { + return fileName + DOT_CHECKSUM; + } + + private void handleChecksumValidation(String fileName, MultipartFile file, String checkSum, String assetid) { + try (InputStream inputStream = file.getInputStream()) { + /* validate */ + assertCheckSumCorrect(checkSum, inputStream); + + } catch (IOException e) { + LOG.error("Was not able to validate uploaded file checksum for file '{}' in asset '{}'", fileName, assetid, e); + throw new SecHubRuntimeException("Was not able to validate uploaded asset checksum"); + } + } + + private void persistFileAndChecksumInDatabase(String fileName, byte[] bytes, String checkSum, String assetId) throws IOException { + /* delete if exists */ + AssetFileCompositeKey key = AssetFileCompositeKey.builder().assetId(assetId).fileName(fileName).build(); + repository.deleteById(key); + + AssetFile assetFile = new AssetFile(key); + assetFile.setChecksum(checkSum); + assetFile.setData(bytes); + + repository.save(assetFile); + } + + private void storeStream(String fileName, String checkSum, AssetStorage assetStorage, long fileSize, InputStream inputStream) throws IOException { + assetStorage.store(fileName, inputStream, fileSize); + + long checksumSizeInBytes = checkSum.getBytes().length; + assetStorage.store(createFileNameForChecksum(fileName), new StringInputStream(checkSum), checksumSizeInBytes); + } + +} diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/product/ProductExecutorContextFactory.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/product/ProductExecutorContextFactory.java index 2ff0b373ad..76e0f72049 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/product/ProductExecutorContextFactory.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/product/ProductExecutorContextFactory.java @@ -15,12 +15,12 @@ public class ProductExecutorContextFactory { @Autowired ProductResultTransactionService transactionService; - public ProductExecutorContext create(List formerResults, SecHubExecutionContext executionContext, ProductExecutor productExecutor, + public ProductExecutorContext create(List formerResults, SecHubExecutionContext sechubExecutionContext, ProductExecutor productExecutor, ProductExecutorConfig config) { ProductExecutorContext productExecutorContext = new ProductExecutorContext(config, formerResults); - ProductExecutorCallbackImpl callback = new ProductExecutorCallbackImpl(executionContext, productExecutorContext, transactionService); + ProductExecutorCallbackImpl callback = new ProductExecutorCallbackImpl(sechubExecutionContext, productExecutorContext, transactionService); productExecutorContext.callback = callback; productExecutorContext.useFirstFormerResult(); diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/ScanProjectConfig.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/ScanProjectConfig.java index b27366dc5f..ea46fe873e 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/ScanProjectConfig.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/ScanProjectConfig.java @@ -40,6 +40,11 @@ public class ScanProjectConfig { /* +-----------------------------------------------------------------------+ */ public static final String CLASS_NAME = ScanProjectConfig.class.getSimpleName(); + public static final String PROPERTY_KEY = "key"; + + public static final String QUERY_FIND_ALL_CONFIGURATIONS_FOR_PROJECT = "SELECT c FROM ScanProjectConfig c where c." + PROPERTY_KEY + "." + + ScanProjectConfigCompositeKey.PROPERTY_PROJECT_ID + " =:projectId"; + @EmbeddedId ScanProjectConfigCompositeKey key; @@ -98,6 +103,8 @@ public static class ScanProjectConfigCompositeKey implements Serializable { private static final long serialVersionUID = 8753389792382752253L; + public static final String PROPERTY_PROJECT_ID = "projectId"; + @Column(name = COLUMN_PROJECT_ID, nullable = false) private String projectId; @@ -169,6 +176,12 @@ public boolean equals(Object obj) { return false; return true; } + + @Override + public String toString() { + return "ScanProjectConfigCompositeKey [" + (projectId != null ? "projectId=" + projectId + ", " : "") + + (configId != null ? "configId=" + configId : "") + "]"; + } } @Override @@ -197,4 +210,9 @@ public boolean equals(Object obj) { return true; } + @Override + public String toString() { + return "ScanProjectConfig [" + (key != null ? "key=" + key + ", " : "") + (data != null ? "data=" + data : "") + "]"; + } + } \ No newline at end of file diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/ScanProjectConfigID.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/ScanProjectConfigID.java index 60a80ea7ac..e968f1bf20 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/ScanProjectConfigID.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/ScanProjectConfigID.java @@ -4,6 +4,7 @@ import static com.mercedesbenz.sechub.sharedkernel.util.Assert.*; import com.mercedesbenz.sechub.commons.core.MustBeKeptStable; +import com.mercedesbenz.sechub.commons.model.template.TemplateType; @MustBeKeptStable("You can rename enums, but do not change id parts, because used inside DB!") public enum ScanProjectConfigID { @@ -26,13 +27,15 @@ public enum ScanProjectConfigID { */ PROJECT_ACCESS_LEVEL("project_access_level"), + TEMPLATE_WEBSCAN_LOGIN("template_" + TemplateType.WEBSCAN_LOGIN.name().toLowerCase()), + ; private String id; ScanProjectConfigID(String id) { notNull(id, "config id may not be null!"); - maxLength(id, 20); // because in DB we got only 3x20 defined, so max is 20 + maxLength(id, 60); // in DB we got 3x20 defined, but we have only ascii chars allowed, so 60 is max this.id = id; } diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/ScanProjectConfigRepository.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/ScanProjectConfigRepository.java index 526bfd31b5..2b1deb1f57 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/ScanProjectConfigRepository.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/ScanProjectConfigRepository.java @@ -3,9 +3,13 @@ import static com.mercedesbenz.sechub.domain.scan.project.ScanProjectConfig.*; +import java.util.List; +import java.util.Set; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import com.mercedesbenz.sechub.domain.scan.project.ScanProjectConfig.ScanProjectConfigCompositeKey; @@ -15,4 +19,11 @@ public interface ScanProjectConfigRepository extends JpaRepository configIds, String value); + + @Query(value = ScanProjectConfig.QUERY_FIND_ALL_CONFIGURATIONS_FOR_PROJECT) + List findAllForProject(@Param("projectId") String projectId); + } diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/ScanProjectConfigService.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/ScanProjectConfigService.java index c15d69cb5d..4a785785f2 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/ScanProjectConfigService.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/ScanProjectConfigService.java @@ -2,9 +2,11 @@ package com.mercedesbenz.sechub.domain.scan.project; import java.util.Optional; +import java.util.Set; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import com.mercedesbenz.sechub.domain.scan.ScanAssertService; import com.mercedesbenz.sechub.domain.scan.project.ScanProjectConfig.ScanProjectConfigCompositeKey; @@ -74,10 +76,21 @@ public ScanProjectConfig get(String projectId, ScanProjectConfigID configId, boo } /** - * Set configuration for project (means will persist given change to config) + * Removes project configuration entry * - * @param projectId - * @param configId + * @param projectId project identifier + * @param configId configuration identifier + */ + public void unset(String projectId, ScanProjectConfigID configId) { + set(projectId, configId, null); + } + + /** + * Set configuration for project (means will persist given change to + * configuration) + * + * @param projectId project identifier + * @param configId configuration identifier * @param data when null existing entry will be deleted on * database */ @@ -107,4 +120,9 @@ public void set(String projectId, ScanProjectConfigID configId, String data) { } + @Transactional + public void deleteAllConfigurationsOfGivenConfigIdsAndValue(Set configIds, String value) { + repository.deleteAllConfigurationsOfGivenConfigIdsAndValue(configIds, value); + } + } diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/template/Template.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/template/Template.java new file mode 100644 index 0000000000..c885e3c20a --- /dev/null +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/template/Template.java @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.template; + +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Version; + +/** + * Represents a template + * + * @author Albert Tregnaghi + * + */ +@Entity +@Table(name = Template.TABLE_NAME) +public class Template { + + /* +-----------------------------------------------------------------------+ */ + /* +............................ SQL ......................................+ */ + /* +-----------------------------------------------------------------------+ */ + public static final String TABLE_NAME = "SCAN_TEMPLATE"; + + public static final String COLUMN_TEMPLATE_ID = "TEMPLATE_ID"; + public static final String COLUMN_DEFINITION = "TEMPLATE_DEFINITION"; + + /* +-----------------------------------------------------------------------+ */ + /* +............................ JPQL .....................................+ */ + /* +-----------------------------------------------------------------------+ */ + public static final String CLASS_NAME = "Template"; + + public static final String PROPERTY_ID = "id"; + public static final String PROPERTY_DEFINITION = "definition"; + + public static final String QUERY_All_TEMPLATE_IDS = "select t.id from #{#entityName} t"; + + @Id + @Column(name = COLUMN_TEMPLATE_ID, updatable = false, nullable = false) + String id; + + @Column(name = COLUMN_DEFINITION) + String definition; + + @Version + @Column(name = "VERSION") + @JsonIgnore + Integer version; + + Template() { + // jpa only + } + + public Template(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setDefinition(String definition) { + this.definition = definition; + } + + public String getDefinition() { + return definition; + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Template other = (Template) obj; + return Objects.equals(id, other.id); + } + + @Override + public String toString() { + return "Template [" + (id != null ? "id=" + id + ", " : "") + (definition != null ? "definition=" + definition : "") + "]"; + } + +} diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/template/TemplateProjectAssignment.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/template/TemplateProjectAssignment.java new file mode 100644 index 0000000000..32f5551374 --- /dev/null +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/template/TemplateProjectAssignment.java @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.template; + +import java.util.Objects; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import jakarta.persistence.Version; + +/** + * Represents a template + * + * @author Albert Tregnaghi + * + */ +@Entity +@Table(name = TemplateProjectAssignment.TABLE_NAME) +public class TemplateProjectAssignment { + + /* +-----------------------------------------------------------------------+ */ + /* +............................ SQL ......................................+ */ + /* +-----------------------------------------------------------------------+ */ + public static final String TABLE_NAME = "SCAN_TEMPLATE"; + + public static final String COLUMN_TEMPLATE_ID = "TEMPLATE_ID"; + public static final String COLUMN_DEFINITION = "TEMPLATE_DEFINITION"; + + /* +-----------------------------------------------------------------------+ */ + /* +............................ JPQL .....................................+ */ + /* +-----------------------------------------------------------------------+ */ + public static final String CLASS_NAME = "Template"; + + public static final String PROPERTY_ID = "id"; + public static final String PROPERTY_DEFINITION = "definition"; + + public static final String QUERY_All_TEMPLATE_IDS = "select t.id from #{#entityName} t"; + + @Id + @Column(name = COLUMN_TEMPLATE_ID, updatable = false, nullable = false) + String id; + + @Column(name = COLUMN_DEFINITION) + String definition; + + @Version + @Column(name = "VERSION") + @JsonIgnore + Integer version; + + TemplateProjectAssignment() { + // jpa only + } + + public TemplateProjectAssignment(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setDefinition(String definition) { + this.definition = definition; + } + + public String getDefinition() { + return definition; + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + TemplateProjectAssignment other = (TemplateProjectAssignment) obj; + return Objects.equals(id, other.id); + } + + @Override + public String toString() { + return "Template [" + (id != null ? "id=" + id + ", " : "") + (definition != null ? "definition=" + definition : "") + "]"; + } + +} diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/template/TemplateRepository.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/template/TemplateRepository.java new file mode 100644 index 0000000000..3f5ceeaf88 --- /dev/null +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/template/TemplateRepository.java @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.template; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface TemplateRepository extends JpaRepository { + + @Query(Template.QUERY_All_TEMPLATE_IDS) + List findAllTemplateIds(); +} diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/template/TemplateRestController.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/template/TemplateRestController.java new file mode 100644 index 0000000000..573bf7defd --- /dev/null +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/template/TemplateRestController.java @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.template; + +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Profile; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition; +import com.mercedesbenz.sechub.sharedkernel.Profiles; +import com.mercedesbenz.sechub.sharedkernel.Step; +import com.mercedesbenz.sechub.sharedkernel.logging.AuditLogService; +import com.mercedesbenz.sechub.sharedkernel.logging.LogSanitizer; +import com.mercedesbenz.sechub.sharedkernel.security.APIConstants; +import com.mercedesbenz.sechub.sharedkernel.security.RoleConstants; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminCreatesOrUpdatesTemplate; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminDeletesTemplate; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminFetchesAllTemplateIds; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminFetchesTemplate; + +import jakarta.annotation.security.RolesAllowed; + +@RestController +@EnableAutoConfiguration +@RequestMapping(APIConstants.API_ADMINISTRATION) +@RolesAllowed({ RoleConstants.ROLE_SUPERADMIN }) +@Profile(Profiles.ADMIN_ACCESS) +public class TemplateRestController { + + @Autowired + TemplateService templateService; + + @Autowired + AuditLogService auditLogService; + + @Autowired + LogSanitizer logSanitizer; + + @UseCaseAdminCreatesOrUpdatesTemplate(@Step(number = 1, next = 2, name = "REST API call to create or update template", needsRestDoc = true)) + @RequestMapping(path = "/template/{templateId}", method = RequestMethod.PUT) + @ResponseStatus(HttpStatus.OK) + public void createOrUpdate(@RequestBody TemplateDefinition templateDefinition, @PathVariable("templateId") String templateId) { + + auditLogService.log("starts create/update of template: {}", logSanitizer.sanitize(templateId, -1)); + + templateService.createOrUpdateTemplate(templateId, templateDefinition); + + } + + @UseCaseAdminDeletesTemplate(@Step(number = 1, next = 2, name = "REST API call to delete a template", needsRestDoc = true)) + @RequestMapping(path = "/template/{templateId}", method = RequestMethod.DELETE) + @ResponseStatus(HttpStatus.OK) + public void delete(@PathVariable("templateId") String templateId) { + + auditLogService.log("starts delete of template: {}", logSanitizer.sanitize(templateId, -1)); + + templateService.deleteTemplate(templateId); + + } + + @UseCaseAdminFetchesTemplate(@Step(number = 1, next = 2, name = "REST API call to fetch template", needsRestDoc = true)) + @RequestMapping(path = "/template/{templateId}", method = RequestMethod.GET) + @ResponseStatus(HttpStatus.OK) + public TemplateDefinition fetchTemplate(@PathVariable("templateId") String templateId) { + + auditLogService.log("fetches template definition for template: {}", logSanitizer.sanitize(templateId, -1)); + + return templateService.fetchTemplateDefinition(templateId); + + } + + @UseCaseAdminFetchesAllTemplateIds(@Step(number = 1, next = 2, name = "REST API call to fetch template list", needsRestDoc = true)) + @RequestMapping(path = "/templates", method = RequestMethod.GET) + @ResponseStatus(HttpStatus.OK) + public List fetchAllTemplateIds() { + return templateService.fetchAllTemplateIds(); + + } +} diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/template/TemplateService.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/template/TemplateService.java new file mode 100644 index 0000000000..ad66b38d37 --- /dev/null +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/template/TemplateService.java @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.template; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import com.mercedesbenz.sechub.commons.model.template.TemplateDefinition; +import com.mercedesbenz.sechub.commons.model.template.TemplateType; +import com.mercedesbenz.sechub.domain.scan.project.ScanProjectConfig; +import com.mercedesbenz.sechub.domain.scan.project.ScanProjectConfigID; +import com.mercedesbenz.sechub.domain.scan.project.ScanProjectConfigService; +import com.mercedesbenz.sechub.sharedkernel.Step; +import com.mercedesbenz.sechub.sharedkernel.error.NotFoundException; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminAssignsTemplateToProject; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminCreatesOrUpdatesTemplate; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminDeletesTemplate; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminFetchesAllTemplateIds; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminFetchesTemplate; +import com.mercedesbenz.sechub.sharedkernel.usecases.admin.config.UseCaseAdminUnassignsTemplateFromProject; +import com.mercedesbenz.sechub.sharedkernel.validation.UserInputAssertion; + +@Service +public class TemplateService { + + private static final Logger LOG = LoggerFactory.getLogger(TemplateService.class); + + private final TemplateRepository repository; + + private final ScanProjectConfigService configService; + + private final TemplateTypeScanConfigIdResolver resolver; + + private final UserInputAssertion inputAssertion; + + TemplateService(TemplateRepository repository, ScanProjectConfigService configService, UserInputAssertion inputAssertion, + TemplateTypeScanConfigIdResolver resolver) { + this.repository = repository; + this.configService = configService; + this.resolver = resolver; + this.inputAssertion = inputAssertion; + } + + @UseCaseAdminCreatesOrUpdatesTemplate(@Step(number = 2, name = "Service creates or updates template")) + public void createOrUpdateTemplate(String templateId, TemplateDefinition newTemplateDefinition) { + if (templateId == null) { + throw new IllegalArgumentException("Template id may not be null!"); + } + if (newTemplateDefinition == null) { + throw new IllegalArgumentException("Template definition may not be null!"); + } + + // first of all we always set template id, so we have never a clash here + // even when somebody copied an existing template definition. + newTemplateDefinition.setId(templateId); + + Optional