This is an automated email from the ASF dual-hosted git repository. markap14 pushed a commit to branch NIFI-15258 in repository https://gitbox.apache.org/repos/asf/nifi.git
commit 3a33552408ae7d82857e7ea4ce21e40f85b0e197 Author: Matt Gilman <[email protected]> AuthorDate: Wed Dec 17 14:28:56 2025 -0500 NIFI-15343: Adding an endpoint to return available secrets to the con… (#10645) * NIFI-15343: Adding an endpoint to return available secrets to the connector configuration wizard. * NIFI-15343: Adding providerId to SecretDTO. * NIFI-15343: Skipping ParameterProviders that are invalid or validating when fetching secrets. * NIFI-15343: Adding fully qualified name to SecretDTO. - Fixing rebase issue. This closes #10645 --- .../org/apache/nifi/web/api/dto/SecretDTO.java | 89 ++++++++++++++++++++++ .../apache/nifi/web/api/entity/SecretsEntity.java | 43 +++++++++++ .../secrets/ParameterProviderSecretsManager.java | 6 ++ .../TestParameterProviderSecretsManager.java | 32 ++++++++ .../org/apache/nifi/web/NiFiServiceFacade.java | 13 ++++ .../apache/nifi/web/StandardNiFiServiceFacade.java | 23 ++++++ .../org/apache/nifi/web/api/ConnectorResource.java | 48 ++++++++++++ .../org/apache/nifi/web/api/dto/DtoFactory.java | 23 ++++++ .../org/apache/nifi/web/api/dto/EntityFactory.java | 7 ++ .../nifi/web/controller/ControllerFacade.java | 10 +++ .../nifi/web/StandardNiFiServiceFacadeTest.java | 76 ++++++++++++++++++ .../apache/nifi/web/api/TestConnectorResource.java | 27 +++++++ 12 files changed, 397 insertions(+) diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/SecretDTO.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/SecretDTO.java new file mode 100644 index 0000000000..dbbf0f36a0 --- /dev/null +++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/SecretDTO.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.web.api.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.xml.bind.annotation.XmlType; + +/** + * DTO representing a secret's metadata. Note: The actual secret value is never exposed via the REST API. + */ +@XmlType(name = "secret") +public class SecretDTO { + + private String providerId; + private String providerName; + private String groupName; + private String name; + private String fullyQualifiedName; + private String description; + + @Schema(description = "The identifier of the secret provider that manages this secret.") + public String getProviderId() { + return providerId; + } + + public void setProviderId(final String providerId) { + this.providerId = providerId; + } + + @Schema(description = "The name of the secret provider that manages this secret.") + public String getProviderName() { + return providerName; + } + + public void setProviderName(final String providerName) { + this.providerName = providerName; + } + + @Schema(description = "The name of the group this secret belongs to.") + public String getGroupName() { + return groupName; + } + + public void setGroupName(final String groupName) { + this.groupName = groupName; + } + + @Schema(description = "The name of the secret.") + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + + @Schema(description = "The fully qualified name of the secret, including the group name.") + public String getFullyQualifiedName() { + return fullyQualifiedName; + } + + public void setFullyQualifiedName(final String fullyQualifiedName) { + this.fullyQualifiedName = fullyQualifiedName; + } + + @Schema(description = "A description of the secret.") + public String getDescription() { + return description; + } + + public void setDescription(final String description) { + this.description = description; + } +} + diff --git a/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/SecretsEntity.java b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/SecretsEntity.java new file mode 100644 index 0000000000..0706dde05c --- /dev/null +++ b/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/SecretsEntity.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.nifi.web.api.entity; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.xml.bind.annotation.XmlRootElement; +import org.apache.nifi.web.api.dto.SecretDTO; + +import java.util.List; + +/** + * A serialized representation of this class can be placed in the entity body of a response to the API. + * This particular entity holds a list of secrets. + */ +@XmlRootElement(name = "secretsEntity") +public class SecretsEntity extends Entity { + + private List<SecretDTO> secrets; + + @Schema(description = "The list of secrets available from all secret providers.") + public List<SecretDTO> getSecrets() { + return secrets; + } + + public void setSecrets(final List<SecretDTO> secrets) { + this.secrets = secrets; + } +} + diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretsManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretsManager.java index d4ba733b08..0fe3715555 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretsManager.java +++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/components/connector/secrets/ParameterProviderSecretsManager.java @@ -22,6 +22,8 @@ import org.apache.nifi.components.connector.SecretReference; import org.apache.nifi.controller.ParameterProviderNode; import org.apache.nifi.controller.flow.FlowManager; +import org.apache.nifi.components.validation.ValidationStatus; + import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; @@ -60,6 +62,10 @@ public class ParameterProviderSecretsManager implements SecretsManager { public Set<SecretProvider> getSecretProviders() { final Set<SecretProvider> providers = new HashSet<>(); for (final ParameterProviderNode parameterProviderNode : flowManager.getAllParameterProviders()) { + if (parameterProviderNode.getValidationStatus() != ValidationStatus.VALID) { + continue; + } + providers.add(new ParameterProviderSecretProvider(parameterProviderNode)); } diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/components/connector/secrets/TestParameterProviderSecretsManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/components/connector/secrets/TestParameterProviderSecretsManager.java index b193239ed3..2ccb772f09 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/components/connector/secrets/TestParameterProviderSecretsManager.java +++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/components/connector/secrets/TestParameterProviderSecretsManager.java @@ -18,6 +18,7 @@ package org.apache.nifi.components.connector.secrets; import org.apache.nifi.components.connector.Secret; import org.apache.nifi.components.connector.SecretReference; +import org.apache.nifi.components.validation.ValidationStatus; import org.apache.nifi.controller.ParameterProviderNode; import org.apache.nifi.controller.flow.FlowManager; import org.apache.nifi.parameter.Parameter; @@ -85,9 +86,15 @@ public class TestParameterProviderSecretsManager { } private ParameterProviderNode createMockedParameterProviderNode(final String id, final String name, final String groupName, final Parameter... parameters) { + return createMockedParameterProviderNode(id, name, groupName, ValidationStatus.VALID, parameters); + } + + private ParameterProviderNode createMockedParameterProviderNode(final String id, final String name, final String groupName, + final ValidationStatus validationStatus, final Parameter... parameters) { final ParameterProviderNode node = mock(ParameterProviderNode.class); when(node.getIdentifier()).thenReturn(id); when(node.getName()).thenReturn(name); + when(node.getValidationStatus()).thenReturn(validationStatus); final List<Parameter> parameterList = List.of(parameters); final ParameterGroup group = new ParameterGroup(groupName, parameterList); @@ -319,5 +326,30 @@ public class TestParameterProviderSecretsManager { assertTrue(result.isPresent()); assertEquals(PROVIDER_1_NAME, result.get().getProviderName()); } + + @Test + public void testGetSecretProvidersFiltersOutInvalidProviders() { + final FlowManager flowManager = mock(FlowManager.class); + final ParameterProviderNode validProvider = createMockedParameterProviderNode("valid-id", "Valid Provider", "Group", + ValidationStatus.VALID, createParameter("secret", "description", "value")); + final ParameterProviderNode invalidProvider = createMockedParameterProviderNode("invalid-id", "Invalid Provider", "Group", + ValidationStatus.INVALID, createParameter("secret2", "description2", "value2")); + final ParameterProviderNode validatingProvider = createMockedParameterProviderNode("validating-id", "Validating Provider", "Group", + ValidationStatus.VALIDATING, createParameter("secret3", "description3", "value3")); + + final Set<ParameterProviderNode> allProviders = new HashSet<>(); + allProviders.add(validProvider); + allProviders.add(invalidProvider); + allProviders.add(validatingProvider); + when(flowManager.getAllParameterProviders()).thenReturn(allProviders); + + final ParameterProviderSecretsManager manager = new ParameterProviderSecretsManager(); + manager.initialize(new StandardSecretsManagerInitializationContext(flowManager)); + + final Set<SecretProvider> secretProviders = manager.getSecretProviders(); + + assertEquals(1, secretProviders.size()); + assertEquals("valid-id", secretProviders.iterator().next().getProviderId()); + } } diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java index 7fcef99ee2..7e8a53c8a1 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java @@ -161,6 +161,7 @@ import org.apache.nifi.web.api.entity.StatusHistoryEntity; import org.apache.nifi.web.api.entity.TenantsEntity; import org.apache.nifi.web.api.entity.UserEntity; import org.apache.nifi.web.api.entity.UserGroupEntity; +import org.apache.nifi.web.api.entity.SecretsEntity; import org.apache.nifi.web.api.entity.VersionControlComponentMappingEntity; import org.apache.nifi.web.api.entity.VersionControlInformationEntity; import org.apache.nifi.web.api.entity.VersionedFlowEntity; @@ -3197,4 +3198,16 @@ public interface NiFiServiceFacade { * @return the list of listen Ports accessible to the current user */ Set<ListenPortDTO> getListenPorts(NiFiUser user); + + // ---------------------------------------- + // Secrets methods + // ---------------------------------------- + + /** + * Gets all secrets available from all secret providers. Note: The actual secret values are not included + * in the response for security reasons; only metadata is returned. + * + * @return the secrets entity containing metadata for all available secrets + */ + SecretsEntity getSecrets(); } diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java index 6852c9ee7b..6fb12571f2 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java @@ -80,6 +80,8 @@ import org.apache.nifi.components.ValidationResult; import org.apache.nifi.components.Validator; import org.apache.nifi.components.connector.ConnectorNode; import org.apache.nifi.components.connector.ConnectorUpdateContext; +import org.apache.nifi.components.connector.Secret; +import org.apache.nifi.components.connector.secrets.AuthorizableSecret; import org.apache.nifi.components.state.Scope; import org.apache.nifi.components.state.StateMap; import org.apache.nifi.components.validation.ValidationState; @@ -295,6 +297,7 @@ import org.apache.nifi.web.api.dto.ReportingTaskDTO; import org.apache.nifi.web.api.dto.RequiredPermissionDTO; import org.apache.nifi.web.api.dto.ResourceDTO; import org.apache.nifi.web.api.dto.RevisionDTO; +import org.apache.nifi.web.api.dto.SecretDTO; import org.apache.nifi.web.api.dto.SnippetDTO; import org.apache.nifi.web.api.dto.SystemDiagnosticsDTO; import org.apache.nifi.web.api.dto.TenantDTO; @@ -392,6 +395,7 @@ import org.apache.nifi.web.api.entity.ConnectorEntity; import org.apache.nifi.web.api.entity.ConfigurationStepEntity; import org.apache.nifi.web.api.entity.ConfigurationStepNamesEntity; import org.apache.nifi.web.api.entity.ScheduleComponentsEntity; +import org.apache.nifi.web.api.entity.SecretsEntity; import org.apache.nifi.web.api.entity.SnippetEntity; import org.apache.nifi.web.api.entity.StartVersionControlRequestEntity; import org.apache.nifi.web.api.entity.StatusHistoryEntity; @@ -7773,4 +7777,23 @@ public class StandardNiFiServiceFacade implements NiFiServiceFacade { final ProcessGroup processGroup = processGroupDAO.getProcessGroup(groupId); return getComponents.apply(processGroup); } + + @Override + public SecretsEntity getSecrets() { + final NiFiUser user = NiFiUserUtils.getNiFiUser(); + final List<Secret> secrets = controllerFacade.getAllSecrets(); + final List<SecretDTO> secretDtos = secrets.stream() + .filter(secret -> isSecretAuthorized(secret, user)) + .map(dtoFactory::createSecretDto) + .toList(); + return entityFactory.createSecretsEntity(secretDtos); + } + + private boolean isSecretAuthorized(final Secret secret, final NiFiUser user) { + if (secret instanceof AuthorizableSecret authorizableSecret) { + final AuthorizationResult result = authorizableSecret.checkAuthorization(authorizer, RequestAction.READ, user); + return Result.Approved.equals(result.getResult()); + } + return true; + } } diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java index ffc3fc264d..e5ad85ea1a 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ConnectorResource.java @@ -65,6 +65,7 @@ import org.apache.nifi.web.api.entity.ConnectorRunStatusEntity; import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity; import org.apache.nifi.web.api.entity.ProcessGroupStatusEntity; import org.apache.nifi.web.api.entity.SearchResultsEntity; +import org.apache.nifi.web.api.entity.SecretsEntity; import org.apache.nifi.web.api.concurrent.AsyncRequestManager; import org.apache.nifi.web.api.concurrent.AsynchronousWebRequest; import org.apache.nifi.web.api.concurrent.RequestManager; @@ -313,6 +314,53 @@ public class ConnectorResource extends ApplicationResource { return generateOkResponse(entity).build(); } + /** + * Gets all available secrets from the SecretsManager for configuring a specific connector. + * + * @param id The id of the connector being configured + * @return A secretsEntity containing metadata for all available secrets. + */ + @GET + @Consumes(MediaType.WILDCARD) + @Produces(MediaType.APPLICATION_JSON) + @Path("/{id}/secrets") + @Operation( + summary = "Gets all secrets available for configuring a connector", + description = "Returns metadata for all secrets available from all secret providers. " + + "This endpoint is used when configuring a connector to discover available secrets. " + + "Note: Actual secret values are not included in the response for security reasons.", + responses = { + @ApiResponse(responseCode = "200", content = @Content(schema = @Schema(implementation = SecretsEntity.class))), + @ApiResponse(responseCode = "400", description = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."), + @ApiResponse(responseCode = "401", description = "Client could not be authenticated."), + @ApiResponse(responseCode = "403", description = "Client is not authorized to make this request."), + @ApiResponse(responseCode = "404", description = "The specified resource could not be found."), + @ApiResponse(responseCode = "409", description = "The request was valid but NiFi was not in the appropriate state to process it.") + }, + security = { + @SecurityRequirement(name = "Write - /connectors/{uuid}") + } + ) + public Response getSecrets( + @Parameter( + description = "The connector id.", + required = true + ) + @PathParam("id") final String id) { + + // NOTE: fetching secrets is handled by the node that receives the request and does not need to be replicated + // Secrets are sourced from ParameterProviders which should have consistent configuration across the cluster + + // authorize access - require write permission on the specific connector since this is used for configuration + serviceFacade.authorizeAccess(lookup -> { + final Authorizable connector = lookup.getConnector(id); + connector.authorize(authorizer, RequestAction.WRITE, NiFiUserUtils.getNiFiUser()); + }); + + final SecretsEntity entity = serviceFacade.getSecrets(); + return generateOkResponse(entity).build(); + } + /** * Updates the specified connector. * diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java index 4d26d4d7ab..e48a59325b 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java @@ -80,6 +80,7 @@ import org.apache.nifi.components.connector.ConnectorPropertyGroup; import org.apache.nifi.components.connector.ConnectorValueReference; import org.apache.nifi.components.connector.FrameworkFlowContext; import org.apache.nifi.components.connector.NamedStepConfiguration; +import org.apache.nifi.components.connector.Secret; import org.apache.nifi.components.connector.SecretReference; import org.apache.nifi.components.connector.StepConfiguration; import org.apache.nifi.components.connector.StringLiteralValue; @@ -5436,4 +5437,26 @@ public final class DtoFactory { return dto; } + /** + * Creates a SecretDTO from the specified Secret. Note: The secret value is intentionally not included + * in the DTO for security reasons. + * + * @param secret the secret + * @return the DTO containing only the secret's metadata + */ + public SecretDTO createSecretDto(final Secret secret) { + if (secret == null) { + return null; + } + + final SecretDTO dto = new SecretDTO(); + dto.setProviderId(secret.getProviderId()); + dto.setProviderName(secret.getProviderName()); + dto.setGroupName(secret.getGroupName()); + dto.setName(secret.getName()); + dto.setFullyQualifiedName(secret.getFullyQualifiedName()); + dto.setDescription(secret.getDescription()); + return dto; + } + } diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java index 144f7805fa..9d51b58e65 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/EntityFactory.java @@ -83,6 +83,7 @@ import org.apache.nifi.web.api.entity.RemoteProcessGroupEntity; import org.apache.nifi.web.api.entity.RemoteProcessGroupPortEntity; import org.apache.nifi.web.api.entity.RemoteProcessGroupStatusEntity; import org.apache.nifi.web.api.entity.RemoteProcessGroupStatusSnapshotEntity; +import org.apache.nifi.web.api.entity.SecretsEntity; import org.apache.nifi.web.api.entity.ReportingTaskEntity; import org.apache.nifi.web.api.entity.SnippetEntity; import org.apache.nifi.web.api.entity.StatusHistoryEntity; @@ -921,4 +922,10 @@ public final class EntityFactory { entity.setAllowableValues(allowableValues); return entity; } + + public SecretsEntity createSecretsEntity(final List<SecretDTO> secrets) { + final SecretsEntity entity = new SecretsEntity(); + entity.setSecrets(secrets); + return entity; + } } diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java index c75e827375..63611f6a1f 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/controller/ControllerFacade.java @@ -46,6 +46,7 @@ import org.apache.nifi.components.RequiredPermission; import org.apache.nifi.components.listen.ListenComponent; import org.apache.nifi.components.connector.Connector; import org.apache.nifi.components.connector.ConnectorNode; +import org.apache.nifi.components.connector.Secret; import org.apache.nifi.connectable.Connectable; import org.apache.nifi.connectable.Connection; import org.apache.nifi.connectable.Port; @@ -2004,6 +2005,15 @@ public class ControllerFacade implements Authorizable { return new StandardVersionedReportingTaskImporter(flowController); } + /** + * Gets all secrets from the SecretsManager. + * + * @return list of all secrets available from all secret providers + */ + public List<Secret> getAllSecrets() { + return flowController.getConnectorRepository().getSecretsManager().getAllSecrets(); + } + /* * setters */ diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java index e5270f871b..016e5220ad 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/StandardNiFiServiceFacadeTest.java @@ -38,6 +38,8 @@ import org.apache.nifi.authorization.user.StandardNiFiUser.Builder; import org.apache.nifi.components.PropertyDescriptor; import org.apache.nifi.components.connector.ConnectorNode; import org.apache.nifi.components.connector.FrameworkFlowContext; +import org.apache.nifi.components.connector.Secret; +import org.apache.nifi.components.connector.secrets.AuthorizableSecret; import org.apache.nifi.controller.Counter; import org.apache.nifi.controller.FlowController; import org.apache.nifi.controller.ProcessorNode; @@ -104,6 +106,7 @@ import org.apache.nifi.web.api.entity.ClearBulletinsForGroupResultsEntity; import org.apache.nifi.web.api.entity.ClearBulletinsResultEntity; import org.apache.nifi.web.api.entity.CopyRequestEntity; import org.apache.nifi.web.api.entity.CopyResponseEntity; +import org.apache.nifi.web.api.entity.SecretsEntity; import org.apache.nifi.web.api.entity.ProcessGroupEntity; import org.apache.nifi.web.api.entity.StatusHistoryEntity; import org.apache.nifi.web.api.entity.TenantEntity; @@ -1703,4 +1706,77 @@ public class StandardNiFiServiceFacadeTest { assertNotNull(result); assertEquals(0, result.getBulletinsCleared()); } + + @Test + public void testGetSecretsFiltersUnauthorizedSecrets() { + final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new Builder().identity(USER_1).build())); + SecurityContextHolder.getContext().setAuthentication(authentication); + + final ControllerFacade controllerFacade = mock(ControllerFacade.class); + serviceFacade.setControllerFacade(controllerFacade); + + final AuthorizableSecret authorizedSecret = mock(AuthorizableSecret.class); + when(authorizedSecret.getProviderName()).thenReturn("provider1"); + when(authorizedSecret.getGroupName()).thenReturn("group1"); + when(authorizedSecret.getName()).thenReturn("authorized-secret"); + when(authorizedSecret.getDescription()).thenReturn("An authorized secret"); + when(authorizedSecret.checkAuthorization(any(Authorizer.class), any(), any())).thenReturn(AuthorizationResult.approved()); + + final AuthorizableSecret unauthorizedSecret = mock(AuthorizableSecret.class); + when(unauthorizedSecret.getProviderName()).thenReturn("provider2"); + when(unauthorizedSecret.getGroupName()).thenReturn("group2"); + when(unauthorizedSecret.getName()).thenReturn("unauthorized-secret"); + when(unauthorizedSecret.getDescription()).thenReturn("An unauthorized secret"); + when(unauthorizedSecret.checkAuthorization(any(Authorizer.class), any(), any())).thenReturn(AuthorizationResult.denied()); + + when(controllerFacade.getAllSecrets()).thenReturn(List.of(authorizedSecret, unauthorizedSecret)); + + final SecretsEntity result = serviceFacade.getSecrets(); + + assertNotNull(result); + assertNotNull(result.getSecrets()); + assertEquals(1, result.getSecrets().size()); + assertEquals("authorized-secret", result.getSecrets().get(0).getName()); + } + + @Test + public void testGetSecretsWithNonAuthorizableSecrets() { + final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new Builder().identity(USER_1).build())); + SecurityContextHolder.getContext().setAuthentication(authentication); + + final ControllerFacade controllerFacade = mock(ControllerFacade.class); + serviceFacade.setControllerFacade(controllerFacade); + + final Secret nonAuthorizableSecret = mock(Secret.class); + when(nonAuthorizableSecret.getProviderName()).thenReturn("provider1"); + when(nonAuthorizableSecret.getGroupName()).thenReturn("group1"); + when(nonAuthorizableSecret.getName()).thenReturn("non-authorizable-secret"); + when(nonAuthorizableSecret.getDescription()).thenReturn("A non-authorizable secret"); + + when(controllerFacade.getAllSecrets()).thenReturn(List.of(nonAuthorizableSecret)); + + final SecretsEntity result = serviceFacade.getSecrets(); + + assertNotNull(result); + assertNotNull(result.getSecrets()); + assertEquals(1, result.getSecrets().size()); + assertEquals("non-authorizable-secret", result.getSecrets().get(0).getName()); + } + + @Test + public void testGetSecretsWithEmptyList() { + final Authentication authentication = new NiFiAuthenticationToken(new NiFiUserDetails(new Builder().identity(USER_1).build())); + SecurityContextHolder.getContext().setAuthentication(authentication); + + final ControllerFacade controllerFacade = mock(ControllerFacade.class); + serviceFacade.setControllerFacade(controllerFacade); + + when(controllerFacade.getAllSecrets()).thenReturn(List.of()); + + final SecretsEntity result = serviceFacade.getSecrets(); + + assertNotNull(result); + assertNotNull(result.getSecrets()); + assertTrue(result.getSecrets().isEmpty()); + } } diff --git a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java index e1093d266d..df21a6935e 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java +++ b/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/test/java/org/apache/nifi/web/api/TestConnectorResource.java @@ -35,6 +35,7 @@ import org.apache.nifi.web.api.entity.AllowableValueEntity; import org.apache.nifi.web.api.entity.ConnectorEntity; import org.apache.nifi.web.api.entity.ConnectorPropertyAllowableValuesEntity; import org.apache.nifi.web.api.entity.ConnectorRunStatusEntity; +import org.apache.nifi.web.api.entity.SecretsEntity; import org.apache.nifi.web.api.request.ClientIdParameter; import org.apache.nifi.web.api.request.LongParameter; import org.junit.jupiter.api.BeforeEach; @@ -326,6 +327,32 @@ public class TestConnectorResource { verify(serviceFacade, never()).getConnectorPropertyAllowableValues(anyString(), anyString(), anyString(), anyString(), any()); } + @Test + public void testGetSecrets() { + final SecretsEntity responseEntity = new SecretsEntity(); + responseEntity.setSecrets(List.of()); + + when(serviceFacade.getSecrets()).thenReturn(responseEntity); + + try (Response response = connectorResource.getSecrets(CONNECTOR_ID)) { + assertEquals(200, response.getStatus()); + assertEquals(responseEntity, response.getEntity()); + } + + verify(serviceFacade).authorizeAccess(any(AuthorizeAccess.class)); + verify(serviceFacade).getSecrets(); + } + + @Test + public void testGetSecretsNotAuthorized() { + doThrow(AccessDeniedException.class).when(serviceFacade).authorizeAccess(any(AuthorizeAccess.class)); + + assertThrows(AccessDeniedException.class, () -> connectorResource.getSecrets(CONNECTOR_ID)); + + verify(serviceFacade).authorizeAccess(any(AuthorizeAccess.class)); + verify(serviceFacade, never()).getSecrets(); + } + private ConnectorEntity createConnectorEntity() { final ConnectorEntity entity = new ConnectorEntity();
