Skip to content

Commit

Permalink
Implemented template assignment handling for projects #3522
Browse files Browse the repository at this point in the history
  • Loading branch information
de-jcup committed Oct 25, 2024
1 parent 9f143b2 commit ad4ca93
Show file tree
Hide file tree
Showing 53 changed files with 1,830 additions and 104 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,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................................+ */
/* +-----------------------------------------------------------------------+ */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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_TEMPLATES = "PROJECT_TEMPLATES";

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 .....................................+ */
Expand Down Expand Up @@ -88,6 +91,11 @@ public class Project {
@OneToMany(cascade = { CascadeType.REFRESH }, fetch = FetchType.EAGER, mappedBy = ProjectMetaDataEntity.PROPERTY_PROJECT_ID)
Set<ProjectMetaDataEntity> metaData = new HashSet<>();

@Column(name = COLUMN_TEMPLATES, nullable = false)
@ElementCollection(targetClass = String.class, fetch = FetchType.EAGER)
@CollectionTable(name = TABLE_NAME_PROJECT_TEMPLATES)
Set<String> templates = new HashSet<>();

@Version
@Column(name = "VERSION")
Integer version;
Expand Down Expand Up @@ -127,6 +135,10 @@ public ProjectAccessLevel getAccessLevel() {
return accessLevel;
}

public Set<String> getTemplates() {
return templates;
}

@Override
public int hashCode() {
final int prime = 31;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import com.mercedesbenz.sechub.sharedkernel.RoleConstants;
import com.mercedesbenz.sechub.sharedkernel.Step;
import com.mercedesbenz.sechub.sharedkernel.project.ProjectAccessLevel;
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;
Expand Down Expand Up @@ -86,6 +88,9 @@ public class ProjectAdministrationRestController {
@Autowired
ListProjectsService listProjectsService;

@Autowired
ProjectTemplateService projectTemplateService;

/* @formatter:off */
@UseCaseAdminCreatesProject(
@Step(
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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_TEMPLATES = "templates";

private String projectId;

private List<String> users = new ArrayList<>();
private List<String> whitelist = new ArrayList<>();
private List<String> templates = new ArrayList<>();
private Map<String, String> metaData = new HashMap<>();
private String owner;
private String description;
private String accessLevel;

ProjectDetailInformation() {
/* for JSON */
}

public ProjectDetailInformation(Project project) {
this.projectId = project.getId();

Expand All @@ -38,6 +44,8 @@ public ProjectDetailInformation(Project project) {

project.getMetaData().forEach(entry -> this.metaData.put(entry.key, entry.value));

project.getTemplates().forEach(templateid -> this.templates.add(templateid));

this.owner = project.getOwner().getName();

this.description = project.getDescription();
Expand Down Expand Up @@ -72,4 +80,8 @@ public String getDescription() {
public String getAccessLevel() {
return accessLevel;
}

public List<String> getTemplates() {
return templates;
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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) {
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// 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.RoleConstants;
import com.mercedesbenz.sechub.sharedkernel.Step;
import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubProjectTemplates;
import com.mercedesbenz.sechub.sharedkernel.encryption.SecHubProjectToTemplate;
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.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) {
assertion.assertIsValidTemplateId(templateId);
assertion.assertIsValidProjectId(projectId);

Project project = projectRepository.findOrFailProject(projectId);
Set<String> templates = project.getTemplates();
LOG.debug("Start assigning template '{}' to project '{}'. Formerly assgined templates : {}", templateId, projectId, templates);

SecHubProjectTemplates result = sendAssignRequestAndFetchResult(templateId, projectId);
List<String> newTemplates = result.getTemplateIds();
templates.clear();
templates.addAll(newTemplates);

projectTansactionService.saveInOwnTransaction(project);
LOG.info("Assigned template '{}' to project '{}'", templateId, projectId);

LOG.debug("Project '{}' has now following templates: {}", templateId, projectId, templates);

}

/* @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) {
assertion.assertIsValidTemplateId(templateId);
assertion.assertIsValidProjectId(projectId);

Project project = projectRepository.findOrFailProject(projectId);
Set<String> templates = project.getTemplates();
LOG.debug("Start unassigning template '{}' from project '{}'. Formerly assgined templates : {}", templateId, projectId, templates);

SecHubProjectTemplates result = sendUnassignRequestAndFetchResult(templateId, projectId);
List<String> newTemplates = result.getTemplateIds();
templates.clear();
templates.addAll(newTemplates);

projectTansactionService.saveInOwnTransaction(project);
LOG.info("Unassigned template '{}' from project '{}'", templateId, projectId);

LOG.debug("Project '{}' has now following templates: {}", templateId, projectId, templates);

}

@IsSendingSyncMessage(MessageID.REQUEST_ASSIGN_TEMPLATE_TO_PROJECT)
private SecHubProjectTemplates sendAssignRequestAndFetchResult(String templateId, String projectId) {

DomainMessage message = new DomainMessage(MessageID.REQUEST_ASSIGN_TEMPLATE_TO_PROJECT);
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 assign template to project.\nReason:" + result.getErrorMessage());
}

MessageID messageID = result.getMessageId();
if (!(MessageID.RESULT_ASSIGN_TEMPLATE_TO_PROJECT.equals(messageID))) {
throw new IllegalStateException("Result message id not supported: " + messageID);
}

return result.get(MessageDataKeys.PROJECT_TEMPLATES);

}

@IsSendingSyncMessage(MessageID.REQUEST_UNASSIGN_TEMPLATE_FROM_PROJECT)
private SecHubProjectTemplates sendUnassignRequestAndFetchResult(String templateId, String projectId) {

DomainMessage message = new DomainMessage(MessageID.REQUEST_UNASSIGN_TEMPLATE_FROM_PROJECT);
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 assign template to project.\nReason:" + result.getErrorMessage());
}

MessageID messageID = result.getMessageId();
if (!(MessageID.RESULT_UNASSIGN_TEMPLATE_FROM_PROJECT.equals(messageID))) {
throw new IllegalStateException("Result message id not supported: " + messageID);
}

return result.get(MessageDataKeys.PROJECT_TEMPLATES);

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ public class ProjectAdministrationRestControllerMockTest {
@MockBean
ProjectChangeAccessLevelService projectChangeAccessLevelService;

@MockBean
ProjectTemplateService projectTemplateService;

@Before
public void before() {
when(createProjectInputvalidator.supports(ProjectJsonInput.class)).thenReturn(true);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
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.getTemplates()).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.getTemplates()).contains("template1", "template2");
assertThat(toTest.getMetaData()).containsEntry("key1", "value1");

}

}
Loading

0 comments on commit ad4ca93

Please sign in to comment.