Skip to content

Commit

Permalink
[#1936] Adapter Deletion: Disallow Deletion of Pipelines Using Adapte…
Browse files Browse the repository at this point in the history
…r 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]>
  • Loading branch information
muyangye and bossenti authored Nov 9, 2023
1 parent b1daad1 commit e5bc3a2
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,22 @@
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;
Expand All @@ -47,8 +52,8 @@
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> {
Expand Down Expand Up @@ -164,6 +169,7 @@ public Response deleteAdapter(@PathParam("id") String elementId,
@QueryParam("deleteAssociatedPipelines") @DefaultValue("false")
boolean deleteAssociatedPipelines) {
List<String> pipelinesUsingAdapter = getPipelinesUsingAdapter(elementId);
IPipelineStorage pipelineStorageAPI = StorageDispatcher.INSTANCE.getNoSqlStore().getPipelineStorageAPI();

if (pipelinesUsingAdapter.isEmpty()) {
try {
Expand All @@ -174,25 +180,41 @@ public Response deleteAdapter(@PathParam("id") String elementId,
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();
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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%"
>
Expand All @@ -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>
Expand All @@ -51,6 +57,25 @@ <h4>
</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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ export class DeleteAdapterDialogComponent {
isInProgress = false;
currentStatus: any;
adapterUsedByPipeline = false;
deleteAssociatedPipelines = false;
namesOfPipelinesUsingAdapter = '';
numberOfPipelinesWithAdapter = 0;
namesOfPipelinesNotOwnedByUser = '';

constructor(
private dialogRef: DialogRef<DeleteAdapterDialogComponent>,
Expand All @@ -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)
Expand All @@ -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;
}
Expand Down

0 comments on commit e5bc3a2

Please sign in to comment.