From ba8c04e7999f33a381da68de7533c7c694f192bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Risue=C3=B1o?= Date: Thu, 3 Sep 2026 12:44:35 +0200 Subject: [PATCH] Update catalog item fetch when provisioning to send the shared secret as a header --- openapi/openapi-component_catalog-v1.0.0.yaml | 26 +++++++++++++ .../ApplicationPropertiesConfiguration.java | 12 ++++++ .../facade/ProvisionerActionsApiFacade.java | 2 +- .../services/ComponentCatalogService.java | 11 ++++-- .../server/services/ProvisionService.java | 2 +- src/main/resources/application.yml | 2 + .../ProvisionerActionsApiFacadeTest.java | 37 ++++--------------- .../services/ComponentCatalogServiceTest.java | 36 ++++++++++++++++-- .../server/services/ProvisionServiceTest.java | 21 ++++------- src/test/resources/application-testing.env | 1 + 10 files changed, 100 insertions(+), 50 deletions(-) diff --git a/openapi/openapi-component_catalog-v1.0.0.yaml b/openapi/openapi-component_catalog-v1.0.0.yaml index b0321b8..54f1b67 100644 --- a/openapi/openapi-component_catalog-v1.0.0.yaml +++ b/openapi/openapi-component_catalog-v1.0.0.yaml @@ -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. @@ -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. @@ -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. @@ -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 @@ -1230,6 +1252,7 @@ components: - authors - date - updatedAt + - visible example: id: aSdFam...yCg== slug: myproject_some-repo @@ -1248,6 +1271,9 @@ components: - '@SomeAuthor' - '@SomeOtherAuthor' date: "2021-07-01T00:00:00Z" + updatedAt: 1625097600000 + componentCount: 5 + visible: true CatalogItemUserAction: properties: id: diff --git a/src/main/java/org/opendevstack/component_provisioner/config/ApplicationPropertiesConfiguration.java b/src/main/java/org/opendevstack/component_provisioner/config/ApplicationPropertiesConfiguration.java index 4804520..4ca712a 100644 --- a/src/main/java/org/opendevstack/component_provisioner/config/ApplicationPropertiesConfiguration.java +++ b/src/main/java/org/opendevstack/component_provisioner/config/ApplicationPropertiesConfiguration.java @@ -15,6 +15,12 @@ @Configuration public class ApplicationPropertiesConfiguration { + @Bean("securityProps") + @ConfigurationProperties(prefix = "component-provisioner.security") + public SecurityProps securityProps() { + return SecurityProps.builder().build(); + } + @Bean("awxServiceConfig") @ConfigurationProperties(prefix = "component-provisioner.awx.service") public AWXServiceProps awxServiceProps() { @@ -57,6 +63,12 @@ public AzureAdTokenServiceProps azureAdTokenServiceProps() { return AzureAdTokenServiceProps.builder().build(); } + @Builder // useful for unit testing + @Data + public static class SecurityProps { + private String sharedSecret; + } + @Builder // useful for unit testing @Data public static class ProjectsInfoServicesCacheProps { diff --git a/src/main/java/org/opendevstack/component_provisioner/server/facade/ProvisionerActionsApiFacade.java b/src/main/java/org/opendevstack/component_provisioner/server/facade/ProvisionerActionsApiFacade.java index 85e24f1..5e90590 100644 --- a/src/main/java/org/opendevstack/component_provisioner/server/facade/ProvisionerActionsApiFacade.java +++ b/src/main/java/org/opendevstack/component_provisioner/server/facade/ProvisionerActionsApiFacade.java @@ -384,7 +384,7 @@ public ProvisionActionWrapper addMandatoryCatalogItemParamsIfMissing(ProvisionAc private CatalogItem fetchCatalogItem(ProvisionActionWrapper wrapper) { var accessToken = authenticationProvider.getAccessToken(); - return componentCatalogService.getCatalogItem(accessToken, wrapper.getCatalogItemId(), wrapper.getProjectKey()); + return componentCatalogService.getCatalogItem(accessToken, wrapper.getCatalogItemId(), wrapper.getProjectKey(), true); } private void applyDefaultValue( diff --git a/src/main/java/org/opendevstack/component_provisioner/server/services/ComponentCatalogService.java b/src/main/java/org/opendevstack/component_provisioner/server/services/ComponentCatalogService.java index 641a3d0..d722fb6 100644 --- a/src/main/java/org/opendevstack/component_provisioner/server/services/ComponentCatalogService.java +++ b/src/main/java/org/opendevstack/component_provisioner/server/services/ComponentCatalogService.java @@ -44,15 +44,19 @@ public class ComponentCatalogService { private final ApplicationPropertiesConfiguration.ComponentProvisionerParametersProps parametersProps; + private final ApplicationPropertiesConfiguration.SecurityProps securityProps; + public ComponentCatalogService( CatalogItemUserActionMessageDefinitionsApi itemUserActionMessagesDefinitionsApi, ApiClientsBuilder apiClientsBuilder, ApplicationPropertiesConfiguration.ComponentCatalogServiceProps componentCatalogServiceProps, - @Qualifier("componentProvisionerParametersConfig") ApplicationPropertiesConfiguration.ComponentProvisionerParametersProps parametersProps) { + @Qualifier("componentProvisionerParametersConfig") ApplicationPropertiesConfiguration.ComponentProvisionerParametersProps parametersProps, + ApplicationPropertiesConfiguration.SecurityProps securityProps) { this.itemUserActionMessagesDefinitionsApi = itemUserActionMessagesDefinitionsApi; this.apiClientsBuilder = apiClientsBuilder; this.componentCatalogServiceProps = componentCatalogServiceProps; this.parametersProps = parametersProps; + this.securityProps = securityProps; } public Pair> getCatalogItemUserActionMessageDefinition( @@ -150,11 +154,12 @@ public List getProjectComponents(String accessToken, Strin return componentsApi.getProjectComponents(projectKey); } - public CatalogItem getCatalogItem(String accessToken, String catalogItemId, String projectKey) { + public CatalogItem getCatalogItem(String accessToken, String catalogItemId, String projectKey, boolean ignoreItemVisibilityRestrictions) { var apiClient = apiClientsBuilder.componentCatalogApiClient(accessToken, componentCatalogServiceProps.getBaseRestUrl().toString()); var catalogItemsApi = apiClientsBuilder.catalogItemsApi(apiClient); + var sharedSecret = ignoreItemVisibilityRestrictions ? securityProps.getSharedSecret() : null; - var catalogItem = catalogItemsApi.getCatalogItemByIdForProjectKey(catalogItemId, projectKey); + var catalogItem = catalogItemsApi.getCatalogItemByIdForProjectKey(catalogItemId, projectKey, sharedSecret); log.debug("Retrieved catalog item with id {} for project key {}: {}", catalogItemId, projectKey, catalogItem); diff --git a/src/main/java/org/opendevstack/component_provisioner/server/services/ProvisionService.java b/src/main/java/org/opendevstack/component_provisioner/server/services/ProvisionService.java index 745787e..3a0e038 100644 --- a/src/main/java/org/opendevstack/component_provisioner/server/services/ProvisionService.java +++ b/src/main/java/org/opendevstack/component_provisioner/server/services/ProvisionService.java @@ -96,7 +96,7 @@ public List getDeletionParameters(String projectKey, St var apiClient = apiClientsBuilder.componentCatalogApiClient(authenticationProvider.getAccessToken(), componentCatalogServiceProps.getBaseRestUrl().toString()); var catalogItemsApi = apiClientsBuilder.catalogItemsApi(apiClient); - var catalogItem = catalogItemsApi.getCatalogItemById(catalogItemId); + var catalogItem = catalogItemsApi.getCatalogItemById(catalogItemId, null); return extractDeletionParameters(catalogItem, projectComponent, ActionType.PROVISION.getValue()); } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 253b569..ab162ff 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -37,6 +37,8 @@ component-provisioner: azure: token: url: ${AZURE_TOKEN_URL} + security: + shared-secret: ${SECURITY_SHARED_SECRET} caching: projects-info-services-cache: enabled: true diff --git a/src/test/java/org/opendevstack/component_provisioner/server/facade/ProvisionerActionsApiFacadeTest.java b/src/test/java/org/opendevstack/component_provisioner/server/facade/ProvisionerActionsApiFacadeTest.java index 81b3fad..fa4845d 100644 --- a/src/test/java/org/opendevstack/component_provisioner/server/facade/ProvisionerActionsApiFacadeTest.java +++ b/src/test/java/org/opendevstack/component_provisioner/server/facade/ProvisionerActionsApiFacadeTest.java @@ -18,25 +18,14 @@ import org.opendevstack.component_provisioner.server.controllers.exceptions.ProjectConfigurationException; import org.opendevstack.component_provisioner.server.controllers.exceptions.SlugNotFoundException; import org.opendevstack.component_provisioner.server.controllers.validators.MandatoryFieldType; -import org.opendevstack.component_provisioner.server.controllers.validators.MandatoryFieldsValidator; import org.opendevstack.component_provisioner.server.controllers.validators.ProvisionerActionsApiValidator; import org.opendevstack.component_provisioner.server.mappers.EntitiesMapper; -import org.opendevstack.component_provisioner.server.services.awx.AwxWorkflowJobLaunchMother; -import org.opendevstack.component_provisioner.server.services.awx.AwxWorkflowJobMother; -import org.opendevstack.component_provisioner.server.model.ProvisionAction; -import org.opendevstack.component_provisioner.server.model.ProvisionActionMother; -import org.opendevstack.component_provisioner.server.model.ProvisionActionParameter; -import org.opendevstack.component_provisioner.server.model.ProvisionActionParameterMother; -import org.opendevstack.component_provisioner.server.model.ProvisionActionResponse; -import org.opendevstack.component_provisioner.server.model.ProvisionActionResponseMother; -import org.opendevstack.component_provisioner.server.services.AuthenticationProvider; -import org.opendevstack.component_provisioner.server.services.AwxService; -import org.opendevstack.component_provisioner.server.services.ComponentCatalogService; -import org.opendevstack.component_provisioner.server.services.PlaceholderPostProcessor; -import org.opendevstack.component_provisioner.server.services.ProjectsInfoService; -import org.opendevstack.component_provisioner.server.services.ReplaceParametersService; +import org.opendevstack.component_provisioner.server.model.*; +import org.opendevstack.component_provisioner.server.services.*; import org.opendevstack.component_provisioner.server.services.awx.AwxWorkflowJob; import org.opendevstack.component_provisioner.server.services.awx.AwxWorkflowJobLaunch; +import org.opendevstack.component_provisioner.server.services.awx.AwxWorkflowJobLaunchMother; +import org.opendevstack.component_provisioner.server.services.awx.AwxWorkflowJobMother; import org.springframework.http.HttpStatus; import org.springframework.test.util.ReflectionTestUtils; import org.springframework.web.client.RestClientException; @@ -48,15 +37,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doCallRealMethod; -import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.inOrder; -import static org.mockito.Mockito.lenient; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class ProvisionerActionsApiFacadeTest { @@ -82,9 +64,6 @@ class ProvisionerActionsApiFacadeTest { @Mock private ReplaceParametersService replaceParametersService; - @Mock - private MandatoryFieldsValidator mandatoryFieldsValidator; - @Mock private ProjectsInfoService projectsInfoService; @@ -100,7 +79,7 @@ void bypassAddMissingMandatoryParamsByDefault() { .when(facade) .addMandatoryCatalogItemParamsIfMissing(any(), any()); lenient() - .when(componentCatalogService.getCatalogItem(any(), any(), any())) + .when(componentCatalogService.getCatalogItem(any(), any(), any(), anyBoolean())) .thenReturn(new CatalogItem()); } @@ -939,7 +918,7 @@ void triggerProvisionAction_validatesHiddenMandatoryWorkflowName_beforeWorkflowW var provisionActionResponse = ProvisionActionResponseMother.of(); provisionActionResponse.setId(123); - when(componentCatalogService.getCatalogItem("token", "CAT", "PRJ")).thenReturn(catalogItem); + when(componentCatalogService.getCatalogItem("token", "CAT", "PRJ", true)).thenReturn(catalogItem); when(placeholderPostProcessor.process(any())).thenAnswer(inv -> inv.getArgument(0)); ArgumentCaptor wrapperCaptor = ArgumentCaptor.forClass(ProvisionActionWrapper.class); diff --git a/src/test/java/org/opendevstack/component_provisioner/server/services/ComponentCatalogServiceTest.java b/src/test/java/org/opendevstack/component_provisioner/server/services/ComponentCatalogServiceTest.java index 9c25a6c..76278c7 100644 --- a/src/test/java/org/opendevstack/component_provisioner/server/services/ComponentCatalogServiceTest.java +++ b/src/test/java/org/opendevstack/component_provisioner/server/services/ComponentCatalogServiceTest.java @@ -75,6 +75,9 @@ class ComponentCatalogServiceTest { @Mock private ApplicationPropertiesConfiguration.ComponentProvisionerParametersProps parametersProps; + @Mock + private ApplicationPropertiesConfiguration.SecurityProps securityProps; + @InjectMocks private ComponentCatalogService componentCatalogService; @@ -334,6 +337,7 @@ void givenValidInput_whenGetCatalogItemIsCalled_thenCatalogItemIsReturned() thro String accessToken = "access-token"; String catalogItemId = "CAT-123"; String projectKey = "PRJ-1"; + boolean ignoreItemVisibilityRestrictions = false; URL baseUrl = URI.create("http://component-catalog").toURL(); @@ -345,12 +349,12 @@ void givenValidInput_whenGetCatalogItemIsCalled_thenCatalogItemIsReturned() thro when(apiClientsBuilder.catalogItemsApi(componentCatalogApiClient)) .thenReturn(catalogItemsApi); when(catalogItemsApi.getCatalogItemByIdForProjectKey( - catalogItemId, projectKey)) + catalogItemId, projectKey, null)) .thenReturn(expectedCatalogItem); // when CatalogItem result = componentCatalogService.getCatalogItem( - accessToken, catalogItemId, projectKey); + accessToken, catalogItemId, projectKey, ignoreItemVisibilityRestrictions); // then assertThat(result).isSameAs(expectedCatalogItem); @@ -360,7 +364,7 @@ void givenValidInput_whenGetCatalogItemIsCalled_thenCatalogItemIsReturned() thro verify(apiClientsBuilder) .catalogItemsApi(componentCatalogApiClient); verify(catalogItemsApi) - .getCatalogItemByIdForProjectKey(catalogItemId, projectKey); + .getCatalogItemByIdForProjectKey(catalogItemId, projectKey, null); verifyNoMoreInteractions(catalogItemsApi); verifyNoInteractions( @@ -370,6 +374,32 @@ void givenValidInput_whenGetCatalogItemIsCalled_thenCatalogItemIsReturned() thro ); } + @Test + void givenIgnoredVisibilityRestrictions_whenGetCatalogItemIsCalled_thenSharedSecretIsForwarded() throws MalformedURLException { + // given + var accessToken = "access-token"; + var catalogItemId = "CAT-123"; + var projectKey = "PRJ-1"; + var sharedSecret = "test-shared-secret"; + var baseUrl = URI.create("http://component-catalog").toURL(); + var expectedCatalogItem = new CatalogItem(); + + when(componentCatalogServiceProps.getBaseRestUrl()).thenReturn(baseUrl); + when(apiClientsBuilder.componentCatalogApiClient(accessToken, baseUrl.toString())) + .thenReturn(componentCatalogApiClient); + when(apiClientsBuilder.catalogItemsApi(componentCatalogApiClient)).thenReturn(catalogItemsApi); + when(securityProps.getSharedSecret()).thenReturn(sharedSecret); + when(catalogItemsApi.getCatalogItemByIdForProjectKey(catalogItemId, projectKey, sharedSecret)) + .thenReturn(expectedCatalogItem); + + // when + var result = componentCatalogService.getCatalogItem(accessToken, catalogItemId, projectKey, true); + + // then + assertThat(result).isSameAs(expectedCatalogItem); + verify(catalogItemsApi).getCatalogItemByIdForProjectKey(catalogItemId, projectKey, sharedSecret); + } + @Test void givenValidInput_whenGetProjectComponents_thenProjectComponentsAreReturned() throws Exception { // given diff --git a/src/test/java/org/opendevstack/component_provisioner/server/services/ProvisionServiceTest.java b/src/test/java/org/opendevstack/component_provisioner/server/services/ProvisionServiceTest.java index 7d5a7c6..1b61eb2 100644 --- a/src/test/java/org/opendevstack/component_provisioner/server/services/ProvisionServiceTest.java +++ b/src/test/java/org/opendevstack/component_provisioner/server/services/ProvisionServiceTest.java @@ -8,13 +8,7 @@ import org.opendevstack.component_provisioner.client.component_catalog.v1.ApiClient; import org.opendevstack.component_provisioner.client.component_catalog.v1.api.CatalogItemsApi; import org.opendevstack.component_provisioner.client.component_catalog.v1.api.ProvisionerActionsApi; -import org.opendevstack.component_provisioner.client.component_catalog.v1.model.CatalogItem; -import org.opendevstack.component_provisioner.client.component_catalog.v1.model.CatalogItemUserAction; -import org.opendevstack.component_provisioner.client.component_catalog.v1.model.CatalogItemUserActionParameter; -import org.opendevstack.component_provisioner.client.component_catalog.v1.model.ProjectComponentParameter; -import org.opendevstack.component_provisioner.client.component_catalog.v1.model.ProvisioningDeleteRequest; -import org.opendevstack.component_provisioner.client.component_catalog.v1.model.ProvisioningStatus; -import org.opendevstack.component_provisioner.client.component_catalog.v1.model.ProvisioningStatusUpdateRequest; +import org.opendevstack.component_provisioner.client.component_catalog.v1.model.*; import org.opendevstack.component_provisioner.config.ApplicationPropertiesConfiguration; import org.opendevstack.component_provisioner.server.mappers.CreateIncidentParameterMapper; import org.opendevstack.component_provisioner.server.mappers.EntitiesMapper; @@ -28,6 +22,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -147,7 +142,7 @@ void givenProjectComponent_whenGetDeletionParameters_thenReturnsMappedParameters .id("PROVISION") .parameters(List.of(actionParam)) .build())); - when(catalogItemsApi.getCatalogItemById(any())).thenReturn(catalogItem); + when(catalogItemsApi.getCatalogItemById(any(), isNull())).thenReturn(catalogItem); var projectParam = ProjectComponentParameter.builder() .name("param1") @@ -187,7 +182,7 @@ void givenUserActionsIsNull_whenGetDeletionParameters_thenReturnsEmptyList() thr CatalogItem catalogItem = new CatalogItem(); catalogItem.setUserActions(null); - when(catalogItemsApi.getCatalogItemById(any())).thenReturn(catalogItem); + when(catalogItemsApi.getCatalogItemById(any(), isNull())).thenReturn(catalogItem); // when var result = provisionService.getDeletionParameters(projectKey, componentId); @@ -218,7 +213,7 @@ void givenActionParametersIsNull_whenGetDeletionParameters_thenReturnsEmptyList( catalogItem.setUserActions(List.of(CatalogItemUserAction.builder() .parameters(null) .build())); - when(catalogItemsApi.getCatalogItemById(any())).thenReturn(catalogItem); + when(catalogItemsApi.getCatalogItemById(any(), isNull())).thenReturn(catalogItem); // when var result = provisionService.getDeletionParameters(projectKey, componentId); @@ -252,7 +247,7 @@ void givenSendOnDeletionIsFalse_whenGetDeletionParameters_thenReturnsEmptyList() .sendOnDeletion(false) .build())) .build())); - when(catalogItemsApi.getCatalogItemById(any())).thenReturn(catalogItem); + when(catalogItemsApi.getCatalogItemById(any(), isNull())).thenReturn(catalogItem); // when var result = provisionService.getDeletionParameters(projectKey, componentId); @@ -287,7 +282,7 @@ void givenProjectParametersIsNull_whenGetDeletionParameters_thenReturnsEmptyList .sendOnDeletion(true) .build())) .build())); - when(catalogItemsApi.getCatalogItemById(any())).thenReturn(catalogItem); + when(catalogItemsApi.getCatalogItemById(any(), isNull())).thenReturn(catalogItem); // when var result = provisionService.getDeletionParameters(projectKey, componentId); @@ -321,7 +316,7 @@ void givenParamNameNotFoundInProject_whenGetDeletionParameters_thenReturnsEmptyL .sendOnDeletion(true) .build())) .build())); - when(catalogItemsApi.getCatalogItemById(any())).thenReturn(catalogItem); + when(catalogItemsApi.getCatalogItemById(any(), isNull())).thenReturn(catalogItem); var projectParam = ProjectComponentParameter.builder() .name("param1") diff --git a/src/test/resources/application-testing.env b/src/test/resources/application-testing.env index e75ad7d..2581e70 100644 --- a/src/test/resources/application-testing.env +++ b/src/test/resources/application-testing.env @@ -13,6 +13,7 @@ SPRING_CLOUD_AZURE_ACTIVE_DIRECTORY_CREDENTIAL_CLIENT_ID="api://21920dff-90ff-4b SPRING_SECURITY_PROVISIONER_USERNAME=username SPRING_SECURITY_PROVISIONER_PASSWORD=password +SECURITY_SHARED_SECRET=test-shared-secret COMPONENT_CATALOG_PROVISIONER_USERNAME= COMPONENT_CATALOG_PROVISIONER_PASSWORD=