This is an automated email from the ASF dual-hosted git repository.
pvillard31 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new 28e9f774192 NIFI-16154 Aligned Asset Owner ID checking in Delete and
Get methods (#11485)
28e9f774192 is described below
commit 28e9f774192c072e5b30aa0819925a98331c3fb9
Author: David Handermann <[email protected]>
AuthorDate: Wed Jul 29 13:42:26 2026 -0500
NIFI-16154 Aligned Asset Owner ID checking in Delete and Get methods
(#11485)
---
.../apache/nifi/web/StandardNiFiServiceFacade.java | 20 +++-
.../nifi/web/StandardNiFiServiceFacadeTest.java | 111 +++++++++++++++++++++
2 files changed, 126 insertions(+), 5 deletions(-)
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 3a897f0804c..f725c56b0ba 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
@@ -8499,11 +8499,21 @@ public class StandardNiFiServiceFacade implements
NiFiServiceFacade {
@Override
public void verifyDeleteAsset(final String parameterContextId, final
String assetId) {
- final ParameterContext parameterContext =
parameterContextDAO.getParameterContext(parameterContextId);
- final Set<String> referencingParameterNames =
getReferencingParameterNames(parameterContext, assetId);
- if (!referencingParameterNames.isEmpty()) {
- final String joinedParametersNames = String.join(", ",
referencingParameterNames);
- throw new IllegalStateException("Unable to delete Asset [%s]
because it is currently references by Parameters [%s]".formatted(assetId,
joinedParametersNames));
+ final Asset asset = assetManager.getAsset(assetId)
+ .orElseThrow(
+ () -> new ResourceNotFoundException("Asset [%s] not
found".formatted(assetId))
+ );
+
+ final String ownerIdentifier = asset.getOwnerIdentifier();
+ if (ownerIdentifier.equals(parameterContextId)) {
+ final ParameterContext parameterContext =
parameterContextDAO.getParameterContext(parameterContextId);
+ final Set<String> referencingParameterNames =
getReferencingParameterNames(parameterContext, assetId);
+ if (!referencingParameterNames.isEmpty()) {
+ final String joinedParametersNames = String.join(", ",
referencingParameterNames);
+ throw new IllegalStateException("Unable to delete Asset [%s]
because it is currently references by Parameters [%s]".formatted(assetId,
joinedParametersNames));
+ }
+ } else {
+ throw new ResourceNotFoundException("Asset [%s] not
found".formatted(assetId));
}
}
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 604ac10ceaf..38aea7c7eac 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
@@ -20,6 +20,8 @@ import org.apache.nifi.action.Component;
import org.apache.nifi.action.FlowChangeAction;
import org.apache.nifi.action.Operation;
import org.apache.nifi.admin.service.AuditService;
+import org.apache.nifi.asset.Asset;
+import org.apache.nifi.asset.AssetManager;
import org.apache.nifi.authorization.AccessDeniedException;
import org.apache.nifi.authorization.AuthorizableLookup;
import org.apache.nifi.authorization.AuthorizationRequest;
@@ -76,8 +78,10 @@ import org.apache.nifi.groups.VersionedComponentAdditions;
import org.apache.nifi.history.History;
import org.apache.nifi.history.HistoryQuery;
import org.apache.nifi.nar.ExtensionManager;
+import org.apache.nifi.parameter.Parameter;
import org.apache.nifi.parameter.ParameterContext;
import org.apache.nifi.parameter.ParameterContextLookup;
+import org.apache.nifi.parameter.ParameterDescriptor;
import org.apache.nifi.processor.Processor;
import org.apache.nifi.registry.flow.FlowRegistryClientNode;
import org.apache.nifi.registry.flow.FlowRegistryClientUserContext;
@@ -131,6 +135,7 @@ import org.apache.nifi.web.api.dto.search.SearchResultsDTO;
import org.apache.nifi.web.api.dto.status.StatusHistoryDTO;
import org.apache.nifi.web.api.entity.ActionEntity;
import org.apache.nifi.web.api.entity.AffectedComponentEntity;
+import org.apache.nifi.web.api.entity.AssetEntity;
import org.apache.nifi.web.api.entity.BacklogEntity;
import org.apache.nifi.web.api.entity.ClearBulletinsForGroupResultsEntity;
import org.apache.nifi.web.api.entity.ClearBulletinsResultEntity;
@@ -149,6 +154,7 @@ import org.apache.nifi.web.dao.ComponentStateDAO;
import org.apache.nifi.web.dao.ConnectorDAO;
import org.apache.nifi.web.dao.ConnectorManagedComponentLookup;
import org.apache.nifi.web.dao.FlowRegistryDAO;
+import org.apache.nifi.web.dao.ParameterContextDAO;
import org.apache.nifi.web.dao.ProcessGroupDAO;
import org.apache.nifi.web.dao.ProcessorDAO;
import org.apache.nifi.web.dao.RemoteProcessGroupDAO;
@@ -171,6 +177,7 @@ import
org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
+import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
@@ -249,6 +256,12 @@ public class StandardNiFiServiceFacadeTest {
private static final String PATH_TO_GROUP_2 = "Path2";
private static final String RANDOM_GROUP_ID = "randomGroupId";
+ private static final String ASSET_ID = "asset-1";
+ private static final String ASSET_NAME = "asset-1.bin";
+ private static final String ASSET_PARAMETER_CONTEXT_ID =
"parameter-context-1";
+ private static final String OTHER_PARAMETER_CONTEXT_ID =
"parameter-context-2";
+ private static final String REFERENCING_PARAMETER_NAME = "asset-parameter";
+
private StandardNiFiServiceFacade serviceFacade;
private Authorizer authorizer;
private FlowController flowController;
@@ -2752,4 +2765,102 @@ public class StandardNiFiServiceFacadeTest {
serviceFacade.verifyCanReportConnectorBacklog(connectorId);
}
+
+ @Test
+ public void
testVerifyDeleteAssetWithUnknownAssetIdThrowsResourceNotFound() {
+ final ParameterContext parameterContext = mock(ParameterContext.class);
+ final AssetManager assetManager = configureAssets(null,
parameterContext);
+
+ assertThrows(ResourceNotFoundException.class, () ->
serviceFacade.verifyDeleteAsset(ASSET_PARAMETER_CONTEXT_ID, ASSET_ID));
+
+ verify(parameterContext, never()).getParameters();
+ verify(assetManager, never()).deleteAsset(anyString());
+ }
+
+ @Test
+ public void
testVerifyDeleteAssetOwnedByDifferentContextThrowsResourceNotFound() {
+ final Asset asset = createAsset(ASSET_ID, OTHER_PARAMETER_CONTEXT_ID);
+ final ParameterContext parameterContext = mock(ParameterContext.class);
+ final AssetManager assetManager = configureAssets(asset,
parameterContext);
+
+ assertThrows(ResourceNotFoundException.class, () ->
serviceFacade.verifyDeleteAsset(ASSET_PARAMETER_CONTEXT_ID, ASSET_ID));
+
+ verify(parameterContext, never()).getParameters();
+ verify(assetManager, never()).deleteAsset(anyString());
+ }
+
+ @Test
+ public void testVerifyDeleteAssetOwnedByContextWithNoReferencesSucceeds() {
+ final Asset asset = createAsset(ASSET_ID, ASSET_PARAMETER_CONTEXT_ID);
+ final ParameterContext parameterContext = mock(ParameterContext.class);
+ when(parameterContext.getParameters()).thenReturn(Map.of());
+ configureAssets(asset, parameterContext);
+
+ serviceFacade.verifyDeleteAsset(ASSET_PARAMETER_CONTEXT_ID, ASSET_ID);
+ }
+
+ @Test
+ public void
testVerifyDeleteAssetOwnedByContextThrowsWhenReferencedByParameter() {
+ final Asset asset = createAsset(ASSET_ID, ASSET_PARAMETER_CONTEXT_ID);
+ final ParameterDescriptor descriptor = new
ParameterDescriptor.Builder().name(REFERENCING_PARAMETER_NAME).build();
+ final Parameter parameter = new
Parameter.Builder().descriptor(descriptor).referencedAssets(List.of(asset)).build();
+ final ParameterContext parameterContext = mock(ParameterContext.class);
+ when(parameterContext.getParameters()).thenReturn(Map.of(descriptor,
parameter));
+ final AssetManager assetManager = configureAssets(asset,
parameterContext);
+
+ final IllegalStateException exception =
assertThrows(IllegalStateException.class,
+ () ->
serviceFacade.verifyDeleteAsset(ASSET_PARAMETER_CONTEXT_ID, ASSET_ID));
+
assertTrue(exception.getMessage().contains(REFERENCING_PARAMETER_NAME));
+
+ verify(assetManager, never()).deleteAsset(anyString());
+ }
+
+ @Test
+ public void testDeleteAssetOwnedByDifferentContextDoesNotRemoveAsset() {
+ final Asset asset = createAsset(ASSET_ID, OTHER_PARAMETER_CONTEXT_ID);
+ final AssetManager assetManager = configureAssets(asset,
mock(ParameterContext.class));
+
+ assertThrows(ResourceNotFoundException.class, () ->
serviceFacade.deleteAsset(ASSET_PARAMETER_CONTEXT_ID, ASSET_ID));
+
+ verify(assetManager, never()).deleteAsset(anyString());
+ }
+
+ @Test
+ public void testDeleteAssetOwnedByContextRemovesAsset() {
+ final Asset asset = createAsset(ASSET_ID, ASSET_PARAMETER_CONTEXT_ID);
+ when(asset.getDigest()).thenReturn(Optional.empty());
+ final ParameterContext parameterContext = mock(ParameterContext.class);
+ when(parameterContext.getParameters()).thenReturn(Map.of());
+ final AssetManager assetManager = configureAssets(asset,
parameterContext);
+
+ final AssetEntity assetEntity =
serviceFacade.deleteAsset(ASSET_PARAMETER_CONTEXT_ID, ASSET_ID);
+
+ assertNotNull(assetEntity);
+ assertEquals(ASSET_ID, assetEntity.getAsset().getId());
+ verify(assetManager).deleteAsset(ASSET_ID);
+ }
+
+ private Asset createAsset(final String assetId, final String ownerId) {
+ final Asset asset = mock(Asset.class);
+ when(asset.getIdentifier()).thenReturn(assetId);
+ when(asset.getOwnerIdentifier()).thenReturn(ownerId);
+ when(asset.getName()).thenReturn(ASSET_NAME);
+ when(asset.getFile()).thenReturn(new File(ASSET_NAME));
+ return asset;
+ }
+
+ private AssetManager configureAssets(final Asset asset, final
ParameterContext parameterContext) {
+ final AssetManager assetManager = mock(AssetManager.class);
+ if (asset != null) {
+
when(assetManager.getAsset(asset.getIdentifier())).thenReturn(Optional.of(asset));
+
when(assetManager.deleteAsset(asset.getIdentifier())).thenReturn(Optional.of(asset));
+ }
+
+ final ParameterContextDAO parameterContextDAO =
mock(ParameterContextDAO.class);
+
when(parameterContextDAO.getParameterContext(anyString())).thenReturn(parameterContext);
+
+ serviceFacade.setAssetManager(assetManager);
+ serviceFacade.setParameterContextDAO(parameterContextDAO);
+ return assetManager;
+ }
}