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
26 changes: 26 additions & 0 deletions openapi/openapi-component_catalog-v1.0.0.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,12 @@ paths:
schema:
type: string
example: 'aSdFam...yCg=='
- name: X-Shared-Secret
in: header
required: false
schema:
type: string
example: 'SKiPJ...K46h'
responses:
"200":
description: The CatalogItem.
Expand Down Expand Up @@ -579,6 +585,12 @@ paths:
required: true
schema:
type: string
- name: X-Shared-Secret
in: header
required: false
schema:
type: string
example: 'SKiPJ...K46h'
responses:
"200":
description: A list of valid CatalogItems.
Expand Down Expand Up @@ -639,6 +651,12 @@ paths:
required: true
schema:
type: string
- name: X-Shared-Secret
in: header
required: false
schema:
type: string
example: 'SKiPJ...K46h'
responses:
"200":
description: The CatalogItem.
Expand Down Expand Up @@ -1215,6 +1233,10 @@ components:
@com.fasterxml.jackson.annotation.JsonInclude(
com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL
)
visible:
type: boolean
example: true
description: Whether the catalog item is visible for the user requesting it

userActions:
type: array
Expand All @@ -1230,6 +1252,7 @@ components:
- authors
- date
- updatedAt
- visible
example:
id: aSdFam...yCg==
slug: myproject_some-repo
Expand All @@ -1248,6 +1271,9 @@ components:
- '@SomeAuthor'
- '@SomeOtherAuthor'
date: "2021-07-01T00:00:00Z"
updatedAt: 1625097600000
componentCount: 5
visible: true
CatalogItemUserAction:
properties:
id:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
public class ApplicationPropertiesConfiguration {

@Bean("basicAuthUsersConfig")
@ConfigurationProperties(prefix = "component-catalog.security.basic-auth")
public BasicAuthSecurityProps basicAuthSecurityProps() {
return BasicAuthSecurityProps.builder().build();
}

@Bean("securityConfig")
@ConfigurationProperties(prefix = "component-catalog.security")
public SecurityProps securityProps() {
return SecurityProps.builder().build();
Expand Down Expand Up @@ -72,13 +78,19 @@ public CatalogItemUserActionGroupsRestrictionProps catalogItemGroupsRestrictionC

@Bean("catalogItemDefaultConfig")
@ConfigurationProperties(prefix = "catalog-item")
public CatalogItemDefaultProps catalogItemDefaultConfig() {
public CatalogItemDefaultProps catalogItemDefaultConfig() {
return CatalogItemDefaultProps.builder().build();
}

@Builder
@Data
public static class SecurityProps {
private String sharedSecret;
}

@Builder
@Data
public static class BasicAuthSecurityProps {
private UserProps provisioner;
private UserProps cacheAdmin;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@ public class BasicAuthUsersConfiguration {
@Bean
public InMemoryUserDetailsManager userDetailsService(
@Qualifier("basicAuthUsersConfig")
ApplicationPropertiesConfiguration.SecurityProps securityProps
ApplicationPropertiesConfiguration.BasicAuthSecurityProps basicAuthSecurityProps
) {

UserDetails provisionerUser = User
.withUsername(securityProps.getProvisioner().getUsername())
.password("{noop}" + securityProps.getProvisioner().getPassword())
.roles(securityProps.getProvisioner().getRoles().toArray(String[]::new))
.withUsername(basicAuthSecurityProps.getProvisioner().getUsername())
.password("{noop}" + basicAuthSecurityProps.getProvisioner().getPassword())
.roles(basicAuthSecurityProps.getProvisioner().getRoles().toArray(String[]::new))
.build();

UserDetails cacheAdminUser = User
.withUsername(securityProps.getCacheAdmin().getUsername())
.password("{noop}" + securityProps.getCacheAdmin().getPassword())
.roles(securityProps.getCacheAdmin().getRoles().toArray(String[]::new))
.withUsername(basicAuthSecurityProps.getCacheAdmin().getUsername())
.password("{noop}" + basicAuthSecurityProps.getCacheAdmin().getPassword())
.roles(basicAuthSecurityProps.getCacheAdmin().getRoles().toArray(String[]::new))
.build();

return new InMemoryUserDetailsManager(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.opendevstack.component_catalog.config.ApplicationPropertiesConfiguration.SecurityProps;
import org.opendevstack.component_catalog.server.api.CatalogItemsApi;
import org.opendevstack.component_catalog.server.controllers.exceptions.BadRequestException;
import org.opendevstack.component_catalog.server.controllers.exceptions.InvalidRestEntityException;
Expand Down Expand Up @@ -31,6 +32,7 @@ public class CatalogItemsApiController implements CatalogItemsApi {
private final AuthorizationInfo authInfo;
private final CatalogItemsApiFacade catalogItemsApiFacade;
private final AuthenticationFacade authenticationFacade;
private final SecurityProps securityProps;

@Override
public ResponseEntity<List<CatalogItem>> getCatalogItems(SortOrder sortByTitle, String catalogId) {
Expand All @@ -52,7 +54,7 @@ public ResponseEntity<List<CatalogItem>> getCatalogItems(SortOrder sortByTitle,

@Override
public ResponseEntity<List<CatalogItem>> getCatalogItemsForProjectKey(String catalogId, SortOrder sortByTitle,
String projectKey) {
String projectKey, String xSharedSecret) {
log.debug("User '{}' requested catalog items for catalog id and projectKey: '{}', '{}'",
authInfo.getCurrentPrincipalName(), catalogId, projectKey);
try {
Expand All @@ -63,6 +65,7 @@ public ResponseEntity<List<CatalogItem>> getCatalogItemsForProjectKey(String cat
.sortOrder(sortByTitle)
.projectKey(projectKey)
.accessToken(accessToken)
.ignoreVisibilityRestrictions(securityProps.getSharedSecret().equals(xSharedSecret))
.build();

var items = catalogItemsApiFacade.fetchCatalogItems(catalogItemRequestParams);
Expand All @@ -74,11 +77,12 @@ public ResponseEntity<List<CatalogItem>> getCatalogItemsForProjectKey(String cat
}

@Override
public ResponseEntity<CatalogItem> getCatalogItemById(String id) {
public ResponseEntity<CatalogItem> getCatalogItemById(String id, String xSharedSecret) {
log.debug("User '{}' requested catalog item with id: '{}'", authInfo.getCurrentPrincipalName(), id);
try {
var catalogRequestParams = CatalogRequestParams.builder()
.catalogItemId(id)
.ignoreVisibilityRestrictions(securityProps.getSharedSecret().equals(xSharedSecret))
.build();
var catItem = catalogItemsApiFacade.fetchCatalogItem(catalogRequestParams);
if (catItem == null) {
Expand All @@ -93,7 +97,7 @@ public ResponseEntity<CatalogItem> getCatalogItemById(String id) {
}

@Override
public ResponseEntity<CatalogItem> getCatalogItemByIdForProjectKey(String id, String projectKey) {
public ResponseEntity<CatalogItem> getCatalogItemByIdForProjectKey(String id, String projectKey, String xSharedSecret) {
log.debug("User '{}' requested catalog item with id and projectKey: '{}', '{}'",
authInfo.getCurrentPrincipalName(), id, projectKey);
try {
Expand All @@ -103,6 +107,7 @@ public ResponseEntity<CatalogItem> getCatalogItemByIdForProjectKey(String id, St
.catalogItemId(id)
.projectKey(projectKey)
.accessToken(accessToken)
.ignoreVisibilityRestrictions(securityProps.getSharedSecret().equals(xSharedSecret))
.build();
var catItem = catalogItemsApiFacade.fetchCatalogItem(catalogRequestParams);
if (catItem == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ public class CatalogRequestParams {
@Builder.Default
String projectKey = Strings.EMPTY;
String accessToken;
@Builder.Default
boolean ignoreVisibilityRestrictions = false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,17 @@
import org.opendevstack.component_catalog.server.model.CatalogDescriptor;
import org.opendevstack.component_catalog.server.model.CatalogItem;
import org.opendevstack.component_catalog.server.model.CatalogItemFilter;
import org.opendevstack.component_catalog.server.org.opendevstack.component_catalog.server.model.wrapper.CatalogItemWrapper;
import org.opendevstack.component_catalog.server.security.AuthorizationInfo;
import org.opendevstack.component_catalog.server.services.*;
import org.opendevstack.component_catalog.server.services.catalog.CatalogEntity;
import org.opendevstack.component_catalog.server.services.catalog.CatalogEntityMetadata;
import org.opendevstack.component_catalog.server.services.catalog.CatalogEntityPermissionEnum;
import org.opendevstack.component_catalog.server.services.catalog.CatalogServiceAdapter;
import org.opendevstack.component_catalog.server.services.catalog.InvalidCatalogEntityException;
import org.opendevstack.component_catalog.server.services.catalog.InvalidCatalogItemEntityException;
import org.opendevstack.component_catalog.server.services.catalog.*;
import org.opendevstack.component_catalog.server.services.exceptions.InvalidEntityException;
import org.opendevstack.component_catalog.server.services.exceptions.InvalidIdException;
import org.opendevstack.component_catalog.server.services.filters.CatalogItemsFilter;
import org.opendevstack.component_catalog.server.services.provisioner.ProjectComponents;
import org.opendevstack.component_catalog.server.services.slug.CatalogItemSlug;
import org.opendevstack.component_catalog.util.JwtUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand All @@ -54,13 +41,9 @@ public class CatalogItemsApiFacade {
private final CatalogsCollectionService catalogsCollectionService;
private final OdsApiServerServiceProps odsApiServerServiceProps;
private final ProjectComponentsService projectComponentsService;
private final RolesWhitelistedService rolesWhitelistedService;

private final ProvisionerActionsService provisionerActionsService;
private final AuthenticationFacade authenticationFacade;

private final List<CatalogItemsFilter> catalogItemFilters;

public CatalogItem asCatalogItem(CatalogRequestParams catalogRequestParams) {
var tokenizedCatalogRequestParams = tokenize(catalogRequestParams);
return asCatalogItemWithoutMandatoryToken(tokenizedCatalogRequestParams);
Expand Down Expand Up @@ -165,7 +148,7 @@ private List<CatalogItem> fetchCatalogItemsByCatalogId(CatalogRequestParams cata
)
)
.filter(Objects::nonNull)
.filter(item -> applyFilters(item, catalogRequestParams.getProjectKey()))
.filter(item -> applyVisibilityFilter(item, catalogRequestParams.isIgnoreVisibilityRestrictions()))
.sorted(fieldSorter(CatalogItem::getTitle, catalogRequestParams.getSortOrder()))
.toList();
else {
Expand All @@ -188,7 +171,7 @@ public CatalogItem fetchCatalogItem(CatalogRequestParams catalogRequestParams)
.build()
)
)
.filter(item -> applyFilters(item, catalogRequestParams.getProjectKey()))
.filter(item -> applyVisibilityFilter(item, catalogRequestParams.isIgnoreVisibilityRestrictions()))
.orElse(null);
}

Expand All @@ -214,12 +197,8 @@ public CatalogItem fetchCatalogItemBySlug(CatalogItemSlug slug)
);
}

// We can not do workflowsFilter in here, because the parameters merge was already done
protected boolean applyFilters(CatalogItem item, String projectKey) {
var params = Collections.singletonList(projectKey);

return catalogItemFilters.stream()
.allMatch(filter -> filter.filter(item, params));
protected boolean applyVisibilityFilter(CatalogItem item, boolean ignoreVisibilityRestrictions) {
return ignoreVisibilityRestrictions || item.getVisible();
}

protected boolean filterByContributingFileExists(String id) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.openapitools.jackson.nullable.JsonNullable;
import org.opendevstack.component_catalog.server.services.filters.CatalogItemsFilter;
import org.springframework.stereotype.Component;

import java.util.*;
Expand All @@ -31,6 +32,7 @@
@AllArgsConstructor
public class EntitiesMapper {
private final CatalogItemUserActionMapper catalogItemUserActionMapper;
private final List<CatalogItemsFilter> catalogItemFilters;

public Catalog asCatalog(CatalogEntityContext catalogEntityCtx) {
log.debug("Mapping CatalogEntityContext to Catalog: {}", catalogEntityCtx);
Expand Down Expand Up @@ -89,6 +91,15 @@ public CatalogItemWrapper asCatalogItem(CatalogItemEntityContext catalogItemEnti
? CatalogItemSlug.normalise(catalogProject) + CatalogItemSlug.SEPARATOR + catalogItemEntityCtx.getRepoCatalogItemPathAt().getRepoSlug()
: null;

var isVisible = catalogItemFilters.stream()
.allMatch(filter ->
filter.filter(
CatalogItem.builder()
.id(catalogItemEntityCtx.getId())
.restrictions(catalogItemRestrictions)
.build(),
List.of(projectKey)));

var catalogItem = CatalogItem.builder()
.id(catalogItemEntityCtx.getId())
.slug(itemSlug)
Expand All @@ -104,6 +115,7 @@ public CatalogItemWrapper asCatalogItem(CatalogItemEntityContext catalogItemEnti
.userActions(catalogItemUserActions)
.restrictions(catalogItemRestrictions)
.componentCount(componentCount)
.visible(isVisible)
.build();

log.debug("Resulting CatalogItem: {}", catalogItem);
Expand Down
22 changes: 12 additions & 10 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,18 @@ user-actions:
default-message-definition-id: ${USER_ACTIONS_DEFAULT_MESSAGE_DEFINITION_ID}
component-catalog:
security:
provisioner:
username: ${SECURITY_PROVISIONER_USERNAME}
password: ${SECURITY_PROVISIONER_PASSWORD}
roles:
- PROVISIONER
cache-admin:
username: ${SECURITY_CACHE_ADMIN_USERNAME}
password: ${SECURITY_CACHE_ADMIN_PASSWORD}
roles:
- CACHE_ADMIN
shared-secret: ${SECURITY_SHARED_SECRET}
basic-auth:
provisioner:
username: ${SECURITY_PROVISIONER_USERNAME}
password: ${SECURITY_PROVISIONER_PASSWORD}
roles:
- PROVISIONER
cache-admin:
username: ${SECURITY_CACHE_ADMIN_USERNAME}
password: ${SECURITY_CACHE_ADMIN_PASSWORD}
roles:
- CACHE_ADMIN
caching:
bitbucket-service-cache:
enabled: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.apache.logging.log4j.util.Strings;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.opendevstack.component_catalog.config.ApplicationPropertiesConfiguration;
import org.opendevstack.component_catalog.server.mappers.CatalogItemUserActionMapper;
import org.opendevstack.component_catalog.server.mappers.CatalogItemUserActionParameterMapper;
Expand Down Expand Up @@ -36,6 +37,7 @@
import org.opendevstack.component_catalog.server.services.catalog.entity.CatalogItemEntityContextMother;
import org.opendevstack.component_catalog.server.services.catalog.entity.CatalogItemEntityMother;
import org.opendevstack.component_catalog.server.services.catalog.entity.CatalogItemEntityUserAction;
import org.opendevstack.component_catalog.server.services.filters.CatalogItemsFilter;
import org.opendevstack.component_catalog.server.services.restrictions.evaluators.RestrictionsEvaluator;
import org.opendevstack.component_catalog.server.services.restrictions.evaluators.RestrictionsEvaluatorResultMother;

Expand Down Expand Up @@ -84,7 +86,9 @@ void setUp() {
List.of(dummyEvaluator),
groupsRestrictionProps
);
var entitiesMapper = new EntitiesMapper(catalogItemUserActionMapper);
CatalogItemsFilter filter = Mockito.mock(CatalogItemsFilter.class);

var entitiesMapper = new EntitiesMapper(catalogItemUserActionMapper, List.of(filter));

catalogApiAdapter = new CatalogApiAdapter(
entitiesMapper,
Expand Down
Loading
Loading