This is an automated email from the ASF dual-hosted git repository.
zehnder pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/streampipes.git
The following commit(s) were added to refs/heads/dev by this push:
new e5bc3a288 [#1936] Adapter Deletion: Disallow Deletion of Pipelines
Using Adapter but Not Owned by User (#2139)
e5bc3a288 is described below
commit e5bc3a288ddb5da2652db9442327b58b7a2bc04a
Author: Muyang Ye <[email protected]>
AuthorDate: Wed Nov 8 23:22:02 2023 -0800
[#1936] Adapter Deletion: Disallow Deletion of Pipelines Using Adapter but
Not Owned by User (#2139)
* implement new round processor
* add English locale, icon, and documentation
* fix checkstyle
* support different rounding modes
* add rounding mode in documentation
* fix time display
* let NaryMapping selection account for property scope
* implement boolean filter unit tests
* add common StoreEventCollector class and refactor
TestChangedValueDetectionProcessor
* add new class
* show associated pipelines' names and allow one click deletion
* center text
* fix minor error
* replace magic number
* add timeout
* restore newline
* changeb baseurl
* revert port
* revert timeout
* implement pipelines owner check
* undo automatic changes
* enable admin to delete pipelines no matter ownership
---------
Co-authored-by: bossenti <[email protected]>
---
.../rest/impl/connect/AdapterResource.java | 56 +++++++++++++++-------
.../delete-adapter-dialog.component.html | 29 ++++++++++-
.../delete-adapter-dialog.component.ts | 10 +++-
3 files changed, 74 insertions(+), 21 deletions(-)
diff --git
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/AdapterResource.java
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/AdapterResource.java
index d02e60b69..4980bd993 100644
---
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/AdapterResource.java
+++
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/AdapterResource.java
@@ -22,17 +22,22 @@ import
org.apache.streampipes.commons.exceptions.connect.AdapterException;
import
org.apache.streampipes.connect.management.management.AdapterMasterManagement;
import
org.apache.streampipes.connect.management.management.AdapterUpdateManagement;
import org.apache.streampipes.manager.pipeline.PipelineManager;
+import org.apache.streampipes.model.client.user.Permission;
+import org.apache.streampipes.model.client.user.Role;
import org.apache.streampipes.model.connect.adapter.AdapterDescription;
import org.apache.streampipes.model.message.Notifications;
import org.apache.streampipes.model.monitoring.SpLogMessage;
+import org.apache.streampipes.resource.management.PermissionResourceManager;
import org.apache.streampipes.rest.security.AuthConstants;
import org.apache.streampipes.rest.shared.annotation.JacksonSerialized;
+import org.apache.streampipes.storage.api.IPipelineStorage;
import org.apache.streampipes.storage.management.StorageDispatcher;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.security.core.context.SecurityContextHolder;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.DefaultValue;
@@ -47,8 +52,8 @@ import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
-import java.util.ArrayList;
import java.util.List;
+import java.util.stream.Collectors;
@Path("/v2/connect/master/adapters")
public class AdapterResource extends
AbstractAdapterResource<AdapterMasterManagement> {
@@ -164,6 +169,7 @@ public class AdapterResource extends
AbstractAdapterResource<AdapterMasterManage
@QueryParam("deleteAssociatedPipelines")
@DefaultValue("false")
boolean deleteAssociatedPipelines) {
List<String> pipelinesUsingAdapter = getPipelinesUsingAdapter(elementId);
+ IPipelineStorage pipelineStorageAPI =
StorageDispatcher.INSTANCE.getNoSqlStore().getPipelineStorageAPI();
if (pipelinesUsingAdapter.isEmpty()) {
try {
@@ -174,25 +180,41 @@ public class AdapterResource extends
AbstractAdapterResource<AdapterMasterManage
return ok(Notifications.error(e.getMessage()));
}
} else if (!deleteAssociatedPipelines) {
- List<String> namesOfPipelinesUsingAdapter = new ArrayList<String>();
- for (String pipelineId : pipelinesUsingAdapter) {
- namesOfPipelinesUsingAdapter.add(
-
StorageDispatcher.INSTANCE.getNoSqlStore().getPipelineStorageAPI().getPipeline(pipelineId).getName());
- }
+ List<String> namesOfPipelinesUsingAdapter =
+ pipelinesUsingAdapter.stream().map(pipelineId ->
pipelineStorageAPI.getPipeline(pipelineId).getName())
+ .collect(
+ Collectors.toList());
return Response.status(HttpStatus.SC_CONFLICT).entity(String.join(", ",
namesOfPipelinesUsingAdapter)).build();
} else {
- try {
- // first stop and delete all associated pipelines
- for (String pipelineId : pipelinesUsingAdapter) {
- PipelineManager.stopPipeline(pipelineId, false);
- PipelineManager.deletePipeline(pipelineId);
+ PermissionResourceManager permissionResourceManager = new
PermissionResourceManager();
+ // find out the names of pipelines that have an owner and the owner is
not the current user
+ List<String> namesOfPipelinesNotOwnedByUser =
pipelinesUsingAdapter.stream().filter(pipelineId ->
+
!permissionResourceManager.findForObjectId(pipelineId).stream().findFirst().map(Permission::getOwnerSid)
+ // if a pipeline has no owner, pretend the owner is the user
so the user can delete it
+
.orElse(this.getAuthenticatedUserSid()).equals(this.getAuthenticatedUserSid()))
+ .map(pipelineId ->
pipelineStorageAPI.getPipeline(pipelineId).getName()).collect(Collectors.toList());
+ boolean isAdmin =
SecurityContextHolder.getContext().getAuthentication().getAuthorities().stream()
+ .anyMatch(r -> r.getAuthority().equals(
+ Role.ROLE_ADMIN.name()));
+ // if the user is admin or owns all pipelines using this adapter,
+ // the user can delete all associated pipelines and this adapter
+ if (isAdmin || namesOfPipelinesNotOwnedByUser.isEmpty()) {
+ try {
+ for (String pipelineId : pipelinesUsingAdapter) {
+ PipelineManager.stopPipeline(pipelineId, false);
+ PipelineManager.deletePipeline(pipelineId);
+ }
+ managementService.deleteAdapter(elementId);
+ return ok(Notifications.success(
+ "Adapter with id: " + elementId + " and all pipelines using the
adapter are deleted."));
+ } catch (Exception e) {
+ LOG.error("Error while deleting adapter with id " + elementId + "
and all pipelines using the adapter", e);
+ return ok(Notifications.error(e.getMessage()));
}
- managementService.deleteAdapter(elementId);
- return ok(Notifications.success(
- "Adapter with id: " + elementId + " and all pipelines using the
adapter are deleted."));
- } catch (Exception e) {
- LOG.error("Error while deleting adapter with id " + elementId + " and
all pipelines using the adapter", e);
- return ok(Notifications.error(e.getMessage()));
+ } else {
+ // otherwise, hint the user the names of pipelines using the adapter
but not owned by the user
+ return Response.status(HttpStatus.SC_CONFLICT).entity(String.join(",
", namesOfPipelinesNotOwnedByUser))
+ .build();
}
}
}
diff --git
a/ui/src/app/connect/dialog/delete-adapter-dialog/delete-adapter-dialog.component.html
b/ui/src/app/connect/dialog/delete-adapter-dialog/delete-adapter-dialog.component.html
index f0d5c511e..c5a73031b 100644
---
a/ui/src/app/connect/dialog/delete-adapter-dialog/delete-adapter-dialog.component.html
+++
b/ui/src/app/connect/dialog/delete-adapter-dialog/delete-adapter-dialog.component.html
@@ -19,7 +19,11 @@
<div class="sp-dialog-container">
<div class="sp-dialog-content p-15">
<div
- *ngIf="!isInProgress && adapterUsedByPipeline"
+ *ngIf="
+ !isInProgress &&
+ adapterUsedByPipeline &&
+ !deleteAssociatedPipelines
+ "
fxLayout="column"
style="width: 100%"
>
@@ -28,9 +32,11 @@
<h4>
The adapter is currently used by these pipelines:
{{ namesOfPipelinesUsingAdapter }}
+ (if you don't see those pipelines, they are created by
+ other users)
</h4>
<h4>
- You need to delete these pipelines first before
deleting
+ You need to delete those pipelines first before
deleting
the adapter.
</h4>
<h4>
@@ -51,6 +57,25 @@
</button>
</div>
</div>
+ <div
+ *ngIf="
+ !isInProgress &&
+ adapterUsedByPipeline &&
+ deleteAssociatedPipelines
+ "
+ fxFlex="100"
+ fxLayoutAlign="center center"
+ fxLayout="column"
+ style="width: 100%"
+ >
+ <b>
+ <h4>
+ Unable to delete all associated pipelines because you are
+ not the owner of the following pipelines:
+ {{ namesOfPipelinesNotOwnedByUser }}
+ </h4>
+ </b>
+ </div>
<div *ngIf="!isInProgress && !adapterUsedByPipeline" fxLayout="column">
<div fxFlex="100" fxLayoutAlign="center center" fxLayout="column">
<b
diff --git
a/ui/src/app/connect/dialog/delete-adapter-dialog/delete-adapter-dialog.component.ts
b/ui/src/app/connect/dialog/delete-adapter-dialog/delete-adapter-dialog.component.ts
index 7d61e41cf..5ed5b4ed6 100644
---
a/ui/src/app/connect/dialog/delete-adapter-dialog/delete-adapter-dialog.component.ts
+++
b/ui/src/app/connect/dialog/delete-adapter-dialog/delete-adapter-dialog.component.ts
@@ -35,8 +35,9 @@ export class DeleteAdapterDialogComponent {
isInProgress = false;
currentStatus: any;
adapterUsedByPipeline = false;
+ deleteAssociatedPipelines = false;
namesOfPipelinesUsingAdapter = '';
- numberOfPipelinesWithAdapter = 0;
+ namesOfPipelinesNotOwnedByUser = '';
constructor(
private dialogRef: DialogRef<DeleteAdapterDialogComponent>,
@@ -50,6 +51,7 @@ export class DeleteAdapterDialogComponent {
deleteAdapter(deleteAssociatedPipelines: boolean) {
this.isInProgress = true;
this.currentStatus = 'Deleting adapter...';
+ this.deleteAssociatedPipelines = deleteAssociatedPipelines;
this.dataMarketplaceService
.deleteAdapter(this.adapter, deleteAssociatedPipelines)
@@ -59,7 +61,11 @@ export class DeleteAdapterDialogComponent {
},
error => {
if (error.status === 409) {
- this.namesOfPipelinesUsingAdapter = error.error;
+ if (deleteAssociatedPipelines) {
+ this.namesOfPipelinesNotOwnedByUser = error.error;
+ } else {
+ this.namesOfPipelinesUsingAdapter = error.error;
+ }
this.adapterUsedByPipeline = true;
this.isInProgress = false;
}