Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ public ExternalServiceProps projectsInfoServiceServiceProps() {
return ExternalServiceProps.builder().build();
}

@Bean("catalogProjectComponentsGroupsRestrictionConfig")
@ConfigurationProperties(prefix = "catalog.project-components.groups-restriction")
public CatalogProjectComponentsGroupsRestrictionProps catalogProjectComponentsGroupsRestrictionConfig() {
return CatalogProjectComponentsGroupsRestrictionProps.builder().build();
}

@Bean("catalogItemGroupsRestrictionConfig")
@ConfigurationProperties(prefix = "catalog.user-action.groups-restriction")
public CatalogItemUserActionGroupsRestrictionProps catalogItemGroupsRestrictionConfig() {
Expand Down Expand Up @@ -72,6 +78,12 @@ public static class BitbucketServiceCacheProps {
private Duration evictionInterval;
}

@Builder
@Data
public static class CatalogProjectComponentsGroupsRestrictionProps {
private List<String> prefix;
}

@Builder
@Data
public static class CatalogItemUserActionGroupsRestrictionProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.opendevstack.component_catalog.config.ApplicationPropertiesConfiguration;
import org.opendevstack.component_catalog.server.controllers.exceptions.ComponentNotFoundException;
import org.opendevstack.component_catalog.server.controllers.exceptions.ForbiddenException;
import org.opendevstack.component_catalog.server.mappers.ProjectComponentExtendedInfoMapper;
import org.opendevstack.component_catalog.server.mappers.ProjectComponentsInfoMapper;
import org.opendevstack.component_catalog.server.model.ProjectComponentExtendedInfo;
Expand All @@ -15,10 +17,7 @@
import org.opendevstack.component_catalog.server.services.provisioner.ProjectComponents;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.*;

@Component
@AllArgsConstructor
Expand All @@ -29,6 +28,7 @@ public class ProjectComponentsFacade {
private final ProjectComponentsInfoMapper projectComponentsInfoMapper;
private final ProjectsInfoService projectsInfoService;
private final ProjectComponentExtendedInfoMapper projectComponentExtendedInfoMapper;
private ApplicationPropertiesConfiguration.CatalogProjectComponentsGroupsRestrictionProps catalogProjectComponentsGroupsRestrictionProps;

public List<ProjectComponentInfo> getProjectComponentsInfo(String projectKey, String accessToken) {
var projectComponents = provisionerActionsService.getProjectComponents(projectKey);
Expand All @@ -39,6 +39,10 @@ public List<ProjectComponentInfo> getProjectComponentsInfo(String projectKey, St

List<String> userGroups = projectsInfoService.getProjectGroups(accessToken);

if (!userBelongsToProjectGroups(userGroups, projectKey)) {
throw new ForbiddenException("User must belong to the project to get its components");
}

return projectComponents.getComponents()
.values()
.stream()
Expand All @@ -61,6 +65,11 @@ public ProjectComponentExtendedInfo getProjectComponentExtendedInfo(String proje
throw new IllegalArgumentException("Valid projectKey, componentId and accessToken are mandatory.");
}

List<String> userGroups = projectsInfoService.getProjectGroups(accessToken);
if (!userBelongsToProjectGroups(userGroups, projectKey)) {
throw new ForbiddenException("User must belong to the project to get its components");
}

return Optional.ofNullable(projectComponents.getComponents())
.orElse(Map.of())
.values()
Expand All @@ -78,4 +87,12 @@ private boolean notValid(ProjectComponents projectComponents, String projectKey,
projectComponents.getComponents().isEmpty() || StringUtils.isBlank(accessToken) ||
StringUtils.isBlank(projectKey));
}

private boolean userBelongsToProjectGroups(List<String> groups, String projectKey) {
if (groups == null) return false;
return groups.stream()
.filter(Objects::nonNull)
.anyMatch(g -> catalogProjectComponentsGroupsRestrictionProps.getPrefix().stream().anyMatch(g.toUpperCase()::startsWith) &&
g.toUpperCase().contains(projectKey.toUpperCase()));
}
}
4 changes: 4 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ component-catalog:
base-rest-url: ${PROJECTS_INFO_SERVICE_BASE_REST_URL}

catalog:
project-components:
groups-restriction:
prefix:
- BI-AS-ATLASSIAN
user-action:
groups-restriction:
prefix:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,18 @@ class ProjectComponentsFacadeTest {
@Mock
private ProjectComponentExtendedInfoMapper projectComponentExtendedInfoMapper;

@Mock
private ApplicationPropertiesConfiguration.CatalogProjectComponentsGroupsRestrictionProps catalogGroupsRestrictionProps;

@BeforeEach
void setUp() {
ProjectComponentsInfoMapper projectComponentsInfoMapper = new ProjectComponentsInfoMapper(catalogItemsApiFacade,
catalogItemDefaultProps);
projectComponentsFacade = new ProjectComponentsFacade(provisionerActionsService, projectComponentsInfoMapper, projectsInfoService, projectComponentExtendedInfoMapper);
projectComponentsFacade = new ProjectComponentsFacade(provisionerActionsService, projectComponentsInfoMapper,
projectsInfoService, projectComponentExtendedInfoMapper, catalogGroupsRestrictionProps);

lenient().when(authenticationFacade.getAccessToken()).thenReturn("accessToken");
lenient().when(catalogGroupsRestrictionProps.getPrefix()).thenReturn(List.of("BI-AS-ATLASSIAN-P-"));
}

@Test
Expand All @@ -79,6 +84,7 @@ void givenProjectWithTwoComponents_whenAllCatalogFetchOk_thenReturnMappedList()
var pc = ProjectComponentsMother.of(comps);

when(provisionerActionsService.getProjectComponents(projectKey)).thenReturn(pc);
when(projectsInfoService.getProjectGroups(accessToken)).thenReturn(List.of("BI-AS-ATLASSIAN-P-" + projectKey));
when(catalogItemsApiFacade.fetchCatalogItem(any()))
.thenAnswer(inv -> {
var p = (CatalogRequestParams) inv.getArgument(0);
Expand Down Expand Up @@ -128,6 +134,7 @@ void givenOneComponentFailsWithInvalidIdException_whenGetProjectComponentsInfo_t

var pc = ProjectComponentsMother.of(comps);
when(provisionerActionsService.getProjectComponents(projectKey)).thenReturn(pc);
when(projectsInfoService.getProjectGroups(accessToken)).thenReturn(List.of("BI-AS-ATLASSIAN-P-" + projectKey));
when(catalogItemsApiFacade.fetchCatalogItem(any()))
.thenAnswer(inv -> {
var p = (CatalogRequestParams) inv.getArgument(0);
Expand Down Expand Up @@ -168,6 +175,7 @@ void givenOneComponentFailsWithInvalidCatalogItemEntityException_whenGetProjectC

var pc = ProjectComponentsMother.of(comps);
when(provisionerActionsService.getProjectComponents(projectKey)).thenReturn(pc);
when(projectsInfoService.getProjectGroups(accessToken)).thenReturn(List.of("BI-AS-ATLASSIAN-P-" + projectKey));
when(catalogItemsApiFacade.fetchCatalogItem(any()))
.thenAnswer(inv -> {
var p = (CatalogRequestParams) inv.getArgument(0);
Expand Down Expand Up @@ -205,6 +213,7 @@ void givenBlankOrNullImageFileId_whenMapToProjectComponentInfo_thenLogoUrlIsEmpt
var pc = ProjectComponentsMother.of(comps);

when(provisionerActionsService.getProjectComponents(projectKey)).thenReturn(pc);
when(projectsInfoService.getProjectGroups(accessToken)).thenReturn(List.of("BI-AS-ATLASSIAN-P-" + projectKey));
when(catalogItemsApiFacade.fetchCatalogItem(any()))
.thenAnswer(inv -> {
var p = (CatalogRequestParams) inv.getArgument(0);
Expand Down Expand Up @@ -265,6 +274,7 @@ void givenExistingComponent_whenGetExtendedInfo_thenReturnMappedInfo() {
var comps = ProjectComponentsMother.of(Map.of("k1", comp));

when(provisionerActionsService.getProjectComponents(projectKey)).thenReturn(comps);
when(projectsInfoService.getProjectGroups(accessToken)).thenReturn(List.of("BI-AS-ATLASSIAN-P-" + projectKey));
when(projectComponentExtendedInfoMapper.mapToProjectComponentExtendedInfo(comp))
.thenReturn(Optional.of(new ProjectComponentExtendedInfo()));

Expand All @@ -285,6 +295,7 @@ void givenComponentDoesNotExist_whenGetExtendedInfo_thenThrowComponentNotFound()
var comps = ProjectComponentsMother.of(Map.of("k1", comp));

when(provisionerActionsService.getProjectComponents(projectKey)).thenReturn(comps);
when(projectsInfoService.getProjectGroups(accessToken)).thenReturn(List.of("BI-AS-ATLASSIAN-P-" + projectKey));

// when / then
assertThatThrownBy(() ->
Expand All @@ -303,6 +314,7 @@ void givenMapperReturnsEmptyOptional_whenGetExtendedInfo_thenThrowComponentNotFo
var comps = ProjectComponentsMother.of(Map.of("k1", comp));

when(provisionerActionsService.getProjectComponents(projectKey)).thenReturn(comps);
when(projectsInfoService.getProjectGroups(accessToken)).thenReturn(List.of("BI-AS-ATLASSIAN-P-" + projectKey));
when(projectComponentExtendedInfoMapper.mapToProjectComponentExtendedInfo(comp))
.thenReturn(Optional.empty());

Expand All @@ -311,6 +323,167 @@ void givenMapperReturnsEmptyOptional_whenGetExtendedInfo_thenThrowComponentNotFo
projectComponentsFacade.getProjectComponentExtendedInfo(projectKey, componentId, accessToken)
).isInstanceOf(ComponentNotFoundException.class);
}


@Test
void givenNullUserGroups_whenGetProjectComponentsInfo_thenThrowForbiddenException() {
// given
var projectKey = "PRJ-123";
var comps = ProjectComponentsMother.of(new LinkedHashMap<>(Map.of("k1",
ProjectComponentMother.of("C1", "Y2F0LTE", "cmVmLTE", Status.CREATED))));
when(provisionerActionsService.getProjectComponents(projectKey)).thenReturn(comps);
when(projectsInfoService.getProjectGroups(accessToken)).thenReturn(null);

// when / then
assertThatThrownBy(() -> projectComponentsFacade.getProjectComponentsInfo(projectKey, accessToken))
.isInstanceOf(ForbiddenException.class)
.hasMessageContaining("User must belong to the project to get its components");
}

@Test
void givenEmptyUserGroups_whenGetProjectComponentsInfo_thenThrowForbiddenException() {
// given
var projectKey = "PRJ-123";
var comps = ProjectComponentsMother.of(new LinkedHashMap<>(Map.of("k1",
ProjectComponentMother.of("C1", "Y2F0LTE", "cmVmLTE", Status.CREATED))));
when(provisionerActionsService.getProjectComponents(projectKey)).thenReturn(comps);
when(projectsInfoService.getProjectGroups(accessToken)).thenReturn(List.of());

// when / then
assertThatThrownBy(() -> projectComponentsFacade.getProjectComponentsInfo(projectKey, accessToken))
.isInstanceOf(ForbiddenException.class)
.hasMessageContaining("User must belong to the project to get its components");
}

@Test
void givenGroupWithNoMatchingPrefix_whenGetProjectComponentsInfo_thenThrowForbiddenException() {
// given
var projectKey = "PRJ-123";
var comps = ProjectComponentsMother.of(new LinkedHashMap<>(Map.of("k1",
ProjectComponentMother.of("C1", "Y2F0LTE", "cmVmLTE", Status.CREATED))));
when(provisionerActionsService.getProjectComponents(projectKey)).thenReturn(comps);
when(projectsInfoService.getProjectGroups(accessToken)).thenReturn(List.of("WRONG-PREFIX-PRJ-123"));

// when / then
assertThatThrownBy(() -> projectComponentsFacade.getProjectComponentsInfo(projectKey, accessToken))
.isInstanceOf(ForbiddenException.class)
.hasMessageContaining("User must belong to the project to get its components");
}

@Test
void givenGroupWithMatchingPrefixButWrongProject_whenGetProjectComponentsInfo_thenThrowForbiddenException() {
// given
var projectKey = "PRJ-123";
var comps = ProjectComponentsMother.of(new LinkedHashMap<>(Map.of("k1",
ProjectComponentMother.of("C1", "Y2F0LTE", "cmVmLTE", Status.CREATED))));
when(provisionerActionsService.getProjectComponents(projectKey)).thenReturn(comps);
when(projectsInfoService.getProjectGroups(accessToken)).thenReturn(List.of("BI-AS-ATLASSIAN-P-OTHER"));

// when / then
assertThatThrownBy(() -> projectComponentsFacade.getProjectComponentsInfo(projectKey, accessToken))
.isInstanceOf(ForbiddenException.class)
.hasMessageContaining("User must belong to the project to get its components");
}

@Test
void givenGroupWithMatchingPrefixAndProject_whenGetProjectComponentsInfo_thenDoNotThrowForbiddenException() {
// given
var projectKey = "PRJ-123";
var comps = ProjectComponentsMother.of(new LinkedHashMap<>(Map.of("k1",
ProjectComponentMother.of("C1", "Y2F0LTE", "cmVmLTE", Status.CREATED))));
when(provisionerActionsService.getProjectComponents(projectKey)).thenReturn(comps);
when(projectsInfoService.getProjectGroups(accessToken)).thenReturn(List.of("BI-AS-ATLASSIAN-P-PRJ-123"));

// when
List<ProjectComponentInfo> result = projectComponentsFacade.getProjectComponentsInfo(projectKey, accessToken);

// then
assertThat(result).isNotNull();
}


@Test
void givenNullUserGroups_whenGetProjectComponentExtendedInfo_thenThrowForbiddenException() {
// given
var projectKey = "PRJ-123";
var comps = ProjectComponentsMother.of(new LinkedHashMap<>(Map.of("k1",
ProjectComponentMother.of("C1", "Y2F0LTE", "cmVmLTE", Status.CREATED))));
when(provisionerActionsService.getProjectComponents(projectKey)).thenReturn(comps);
when(projectsInfoService.getProjectGroups(accessToken)).thenReturn(null);

// when / then
assertThatThrownBy(() ->
projectComponentsFacade.getProjectComponentExtendedInfo(projectKey, "C1", accessToken))
.isInstanceOf(ForbiddenException.class)
.hasMessageContaining("User must belong to the project to get its components");
}

@Test
void givenEmptyUserGroups_whenGetProjectComponentExtendedInfo_thenThrowForbiddenException() {
// given
var projectKey = "PRJ-123";
var comps = ProjectComponentsMother.of(new LinkedHashMap<>(Map.of("k1",
ProjectComponentMother.of("C1", "Y2F0LTE", "cmVmLTE", Status.CREATED))));
when(provisionerActionsService.getProjectComponents(projectKey)).thenReturn(comps);
when(projectsInfoService.getProjectGroups(accessToken)).thenReturn(List.of());

// when / then
assertThatThrownBy(() ->
projectComponentsFacade.getProjectComponentExtendedInfo(projectKey, "C1", accessToken))
.isInstanceOf(ForbiddenException.class)
.hasMessageContaining("User must belong to the project to get its components");
}

@Test
void givenGroupWithNoMatchingPrefix_whenGetProjectComponentExtendedInfo_thenThrowForbiddenException() {
// given
var projectKey = "PRJ-123";
var comps = ProjectComponentsMother.of(new LinkedHashMap<>(Map.of("k1",
ProjectComponentMother.of("C1", "Y2F0LTE", "cmVmLTE", Status.CREATED))));
when(provisionerActionsService.getProjectComponents(projectKey)).thenReturn(comps);
when(projectsInfoService.getProjectGroups(accessToken)).thenReturn(List.of("WRONG-PREFIX-PRJ-123"));

// when / then
assertThatThrownBy(() ->
projectComponentsFacade.getProjectComponentExtendedInfo(projectKey, "C1", accessToken))
.isInstanceOf(ForbiddenException.class)
.hasMessageContaining("User must belong to the project to get its components");
}

@Test
void givenGroupWithMatchingPrefixButWrongProject_whenGetProjectComponentExtendedInfo_thenThrowForbiddenException() {
// given
var projectKey = "PRJ-123";
var comps = ProjectComponentsMother.of(new LinkedHashMap<>(Map.of("k1",
ProjectComponentMother.of("C1", "Y2F0LTE", "cmVmLTE", Status.CREATED))));
when(provisionerActionsService.getProjectComponents(projectKey)).thenReturn(comps);
when(projectsInfoService.getProjectGroups(accessToken)).thenReturn(List.of("BI-AS-ATLASSIAN-P-OTHER"));

// when / then
assertThatThrownBy(() ->
projectComponentsFacade.getProjectComponentExtendedInfo(projectKey, "C1", accessToken))
.isInstanceOf(ForbiddenException.class)
.hasMessageContaining("User must belong to the project to get its components");
}

@Test
void givenGroupWithMatchingPrefixAndProject_whenGetProjectComponentExtendedInfo_thenDoNotThrowForbiddenException() {
// given
var projectKey = "PRJ-123";
ProjectComponent comp = ProjectComponentMother.of("C1", "Y2F0LTE", "cmVmLTE", Status.CREATED);
var comps = ProjectComponentsMother.of(new LinkedHashMap<>(Map.of("k1", comp)));
when(provisionerActionsService.getProjectComponents(projectKey)).thenReturn(comps);
when(projectsInfoService.getProjectGroups(accessToken)).thenReturn(List.of("BI-AS-ATLASSIAN-P-PRJ-123"));
when(projectComponentExtendedInfoMapper.mapToProjectComponentExtendedInfo(comp))
.thenReturn(Optional.of(new ProjectComponentExtendedInfo()));

// when
ProjectComponentExtendedInfo result = projectComponentsFacade
.getProjectComponentExtendedInfo(projectKey, "C1", accessToken);

// then
assertThat(result).isNotNull();
}
}


Loading