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 da66ce29a4 fix(#3372): Fix time range selector test (#3373)
da66ce29a4 is described below
commit da66ce29a4f74ad1f1810366f3cdea6101074508
Author: Philipp Zehnder <[email protected]>
AuthorDate: Fri Dec 20 14:19:38 2024 +0100
fix(#3372): Fix time range selector test (#3373)
* fix(#3372): Try to reproduce ci error
* fix(#3372): Only activate one test in GitHub action
* fix(#3372): Only activate one test in GitHub action
* fix(#3372): get localized time independent of time zone
* fix(#3372): Change pr build validation
* fix(#3372): Added further changes to the API and provided two more tests
* fix(#3372): Remove Adapter Exception from AdapterMasterManagementTest
* fix(#3372): Fix to save data view configurations
* fix(#3372): Fix delete adapter tests
* fix(#3372): Extract method to validate sidebar entries
* fix(#3372): WIP share adapter test
* fix(#3372): Change tests to share adapters
* fix(#3372): Change tests to share pipelines
* fix(#3372): Add test for pipeline users
* fix(#3372): Fix test testGroupManagement
---
.../management/AdapterMasterManagement.java | 18 +--
.../management/AdapterMasterManagementTest.java | 19 +--
.../apache/streampipes/rest/ResetManagement.java | 50 +++----
.../rest/impl/AdapterMonitoringResource.java | 42 +++++-
.../streampipes/rest/impl/PipelineMonitoring.java | 33 ++++-
.../streampipes/rest/impl/PipelineResource.java | 43 ++++--
.../rest/impl/connect/AbstractAdapterResource.java | 15 ++
.../rest/impl/connect/AdapterResource.java | 152 +++++++++++++--------
.../rest/impl/connect/CompactAdapterResource.java | 6 +-
.../rest/impl/connect/DescriptionResource.java | 6 +
.../rest/impl/connect/GuessResource.java | 12 ++
.../impl/connect/RuntimeResolvableResource.java | 2 +
.../rest/impl/connect/UnitResource.java | 7 +-
.../rest/security/SpPermissionEvaluator.java | 76 ++++++++---
ui/cypress/support/utils/GeneralUtils.ts | 7 +
ui/cypress/support/utils/UserUtils.ts | 19 +++
ui/cypress/support/utils/connect/ConnectUtils.ts | 9 +-
ui/cypress/support/utils/pipeline/PipelineBtns.ts | 8 ++
ui/cypress/support/utils/pipeline/PipelineUtils.ts | 39 +++++-
.../PipelineBtns.ts => user/PermissionUtils.ts} | 27 +++-
.../support/utils/userInput/StaticPropertyUtils.ts | 10 +-
.../deleteAdapterWithMultipleUsers.smoke.spec.ts | 75 ----------
.../connect/enhancedAdapterDeletion.smoke.spec.ts | 43 ++++++
.../tests/datalake/timeRangeSelectors.spec.ts | 5 +-
.../pipeline/multiUser/pipelineMultiUserSupport.ts | 49 +++++++
.../tests/pipeline/pipelineTest.smoke.spec.ts | 23 +---
.../pipeline/updatePipelineTest.smoke.spec.ts | 23 +---
.../userManagement/testGroupManagement.spec.ts | 16 +--
.../userManagement/testUserRoleConnect.spec.ts | 67 ++++-----
.../userManagement/testUserRolePipeline.spec.ts | 116 ++++++++--------
.../testVariousUserRoles.smoke.spec.ts | 31 +----
.../components/sp-table/sp-table.component.html | 6 +-
.../existing-adapters.component.html | 1 +
.../object-permission-dialog.component.html | 9 +-
.../object-permission-dialog.component.ts | 2 +-
.../pipeline-overview.component.html | 4 +-
36 files changed, 657 insertions(+), 413 deletions(-)
diff --git
a/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/AdapterMasterManagement.java
b/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/AdapterMasterManagement.java
index 5c42478431..79310b96b1 100644
---
a/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/AdapterMasterManagement.java
+++
b/streampipes-connect-management/src/main/java/org/apache/streampipes/connect/management/management/AdapterMasterManagement.java
@@ -97,7 +97,7 @@ public class AdapterMasterManagement {
var storedDescription = new SourcesManagement()
.createAdapterDataStream(adapterDescription, streamId);
storedDescription.setCorrespondingAdapterId(adapterId);
- installDataSource(storedDescription, principalSid, true);
+ installDataSource(storedDescription, principalSid);
LOG.info("Install source (source URL: {} in backend",
adapterDescription.getElementId());
}
@@ -141,15 +141,8 @@ public class AdapterMasterManagement {
LOG.info("Successfully deleted data stream: " +
adapter.getCorrespondingDataStreamElementId());
}
- public List<AdapterDescription> getAllAdapterInstances() throws
AdapterException {
-
- List<AdapterDescription> allAdapters = adapterInstanceStorage.findAll();
-
- if (allAdapters == null) {
- throw new AdapterException("Could not get all adapters");
- }
-
- return allAdapters;
+ public List<AdapterDescription> getAllAdapterInstances() {
+ return adapterInstanceStorage.findAll();
}
public void stopStreamAdapter(String elementId) throws AdapterException {
@@ -198,11 +191,10 @@ public class AdapterMasterManagement {
private void installDataSource(
SpDataStream stream,
- String principalSid,
- boolean publicElement
+ String principalSid
) throws AdapterException {
try {
- new DataStreamVerifier(stream).verifyAndAdd(principalSid, publicElement);
+ new DataStreamVerifier(stream).verifyAndAdd(principalSid, false);
} catch (SepaParseException e) {
LOG.error("Error while installing data source: {}",
stream.getElementId(), e);
throw new AdapterException();
diff --git
a/streampipes-connect-management/src/test/java/org/apache/streampipes/connect/management/management/AdapterMasterManagementTest.java
b/streampipes-connect-management/src/test/java/org/apache/streampipes/connect/management/management/AdapterMasterManagementTest.java
index e1e18324ce..e8095649b8 100644
---
a/streampipes-connect-management/src/test/java/org/apache/streampipes/connect/management/management/AdapterMasterManagementTest.java
+++
b/streampipes-connect-management/src/test/java/org/apache/streampipes/connect/management/management/AdapterMasterManagementTest.java
@@ -71,7 +71,7 @@ public class AdapterMasterManagementTest {
}
@Test
- public void getAllAdapters_Success() throws AdapterException {
+ public void getAllAdapters_Success() {
var adapterDescriptions = List.of(new AdapterDescription());
var adapterStorage = mock(AdapterInstanceStorageImpl.class);
var resourceManager = mock(AdapterResourceManager.class);
@@ -90,21 +90,4 @@ public class AdapterMasterManagementTest {
Assertions.assertEquals(1, result.size());
}
- @Test
- public void getAllAdapters_Fail() {
- var adapterStorage = mock(AdapterInstanceStorageImpl.class);
- var resourceManager = mock(AdapterResourceManager.class);
- when(adapterStorage.findAll()).thenReturn(null);
-
- var adapterMasterManagement =
- new AdapterMasterManagement(
- adapterStorage,
- resourceManager,
- null,
- AdapterMetricsManager.INSTANCE.getAdapterMetrics()
- );
-
- assertThrows(AdapterException.class,
adapterMasterManagement::getAllAdapterInstances);
-
- }
}
diff --git
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/ResetManagement.java
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/ResetManagement.java
index caca0dac6c..0c88e461b6 100644
---
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/ResetManagement.java
+++
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/ResetManagement.java
@@ -108,24 +108,20 @@ public class ResetManagement {
private static void stopAndDeleteAllAdapters() {
AdapterMasterManagement adapterMasterManagement = new
AdapterMasterManagement(
StorageDispatcher.INSTANCE.getNoSqlStore()
- .getAdapterInstanceStorage(),
+ .getAdapterInstanceStorage(),
new SpResourceManager().manageAdapters(),
new SpResourceManager().manageDataStreams(),
AdapterMetricsManager.INSTANCE.getAdapterMetrics()
);
- try {
- List<AdapterDescription> allAdapters =
adapterMasterManagement.getAllAdapterInstances();
- allAdapters.forEach(adapterDescription -> {
- try {
-
adapterMasterManagement.deleteAdapter(adapterDescription.getElementId());
- } catch (AdapterException e) {
- logger.error("Failed to delete adapter with id: " +
adapterDescription.getElementId(), e);
- }
- });
- } catch (AdapterException e) {
- logger.error("Failed to load all adapter descriptions", e);
- }
+ List<AdapterDescription> allAdapters =
adapterMasterManagement.getAllAdapterInstances();
+ allAdapters.forEach(adapterDescription -> {
+ try {
+
adapterMasterManagement.deleteAdapter(adapterDescription.getElementId());
+ } catch (AdapterException e) {
+ logger.error("Failed to delete adapter with id: " +
adapterDescription.getElementId(), e);
+ }
+ });
}
private static void deleteAllFiles() {
@@ -154,37 +150,37 @@ public class ResetManagement {
private static void removeAllDataViewWidgets() {
var widgetStorage =
StorageDispatcher.INSTANCE.getNoSqlStore()
- .getDataExplorerWidgetStorage();
+ .getDataExplorerWidgetStorage();
widgetStorage.findAll()
- .forEach(widget ->
widgetStorage.deleteElementById(widget.getElementId()));
+ .forEach(widget ->
widgetStorage.deleteElementById(widget.getElementId()));
}
private static void removeAllDataViews() {
var dataLakeDashboardStorage =
StorageDispatcher.INSTANCE.getNoSqlStore()
- .getDataExplorerDashboardStorage();
+ .getDataExplorerDashboardStorage();
dataLakeDashboardStorage.findAll()
- .forEach(dashboard ->
dataLakeDashboardStorage.deleteElementById(dashboard.getElementId()));
+ .forEach(dashboard ->
dataLakeDashboardStorage.deleteElementById(dashboard.getElementId()));
}
private static void removeAllDashboardWidgets() {
var dashboardWidgetStorage =
StorageDispatcher.INSTANCE.getNoSqlStore()
- .getDashboardWidgetStorage();
+ .getDashboardWidgetStorage();
dashboardWidgetStorage.findAll()
- .forEach(widget ->
dashboardWidgetStorage.deleteElementById(widget.getElementId()));
+ .forEach(widget ->
dashboardWidgetStorage.deleteElementById(widget.getElementId()));
}
private static void removeAllDashboards() {
var dashboardStorage = StorageDispatcher.INSTANCE.getNoSqlStore()
- .getDashboardStorage();
+ .getDashboardStorage();
dashboardStorage.findAll()
- .forEach(dashboard ->
dashboardStorage.deleteElementById(dashboard.getElementId()));
+ .forEach(dashboard ->
dashboardStorage.deleteElementById(dashboard.getElementId()));
}
private static void removeAllAssets(String username) {
IGenericStorage genericStorage = StorageDispatcher.INSTANCE.getNoSqlStore()
- .getGenericStorage();
+
.getGenericStorage();
try {
for (Map<String, Object> asset :
genericStorage.findAll("asset-management")) {
genericStorage.delete((String) asset.get("_id"), (String)
asset.get("_rev"));
@@ -211,13 +207,19 @@ public class ResetManagement {
"asset-management",
"asset-sites"
);
- var genericStorage =
StorageDispatcher.INSTANCE.getNoSqlStore().getGenericStorage();
+ var genericStorage = StorageDispatcher.INSTANCE.getNoSqlStore()
+ .getGenericStorage();
appDocTypesToDelete.forEach(docType -> {
try {
var allDocs = genericStorage.findAll(docType);
for (var doc : allDocs) {
- genericStorage.delete(doc.get("_id").toString(),
doc.get("_rev").toString());
+ genericStorage.delete(
+ doc.get("_id")
+ .toString(),
+ doc.get("_rev")
+ .toString()
+ );
}
} catch (IOException e) {
throw new RuntimeException(e);
diff --git
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/AdapterMonitoringResource.java
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/AdapterMonitoringResource.java
index 8260c4c9c6..0d7363f781 100644
---
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/AdapterMonitoringResource.java
+++
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/AdapterMonitoringResource.java
@@ -21,11 +21,13 @@ package org.apache.streampipes.rest.impl;
import
org.apache.streampipes.manager.monitoring.pipeline.ExtensionsLogProvider;
import
org.apache.streampipes.manager.monitoring.pipeline.ExtensionsServiceLogExecutor;
+import org.apache.streampipes.model.client.user.DefaultPrivilege;
import org.apache.streampipes.model.monitoring.SpLogEntry;
import org.apache.streampipes.model.monitoring.SpMetricsEntry;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -39,21 +41,51 @@ import java.util.Map;
@RequestMapping("/api/v2/adapter-monitoring")
public class AdapterMonitoringResource extends AbstractMonitoringResource {
- @GetMapping(path = "adapter/{elementId}/logs", produces =
MediaType.APPLICATION_JSON_VALUE)
- public ResponseEntity<List<SpLogEntry>>
getLogInfoForAdapter(@PathVariable("elementId") String elementId) {
+ @GetMapping(
+ path = "adapter/{elementId}/logs",
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ @PreAuthorize("this.hasReadAuthority() and hasPermission('#elementId',
'READ')")
+ public ResponseEntity<List<SpLogEntry>> getLogInfoForAdapter(
+ @PathVariable("elementId") String elementId
+ ) {
return
ok(ExtensionsLogProvider.INSTANCE.getLogInfosForResource(elementId));
}
- @GetMapping(path = "adapter/{elementId}/metrics", produces =
MediaType.APPLICATION_JSON_VALUE)
- public ResponseEntity<SpMetricsEntry>
getMetricsInfoForAdapter(@PathVariable("elementId") String elementId) {
+ @GetMapping(
+ path = "adapter/{elementId}/metrics",
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ @PreAuthorize("this.hasReadAuthority() and hasPermission('#elementId',
'READ')")
+ public ResponseEntity<SpMetricsEntry> getMetricsInfoForAdapter(
+ @PathVariable("elementId") String elementId
+ ) {
return
ok(ExtensionsLogProvider.INSTANCE.getMetricInfosForResource(elementId));
}
- @GetMapping(path = "metrics", produces = MediaType.APPLICATION_JSON_VALUE)
+ @GetMapping(
+ path = "metrics",
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ @PreAuthorize("this.hasReadAuthority() and hasPermission('#elementId',
'READ')")
public ResponseEntity<Map<String, SpMetricsEntry>> getMetricsInfos(
@RequestParam(value = "filter") List<String> elementIds
) {
new ExtensionsServiceLogExecutor().triggerUpdate();
return
ok(ExtensionsLogProvider.INSTANCE.getMetricsInfoForResources(elementIds));
}
+
+ /**
+ * required by Spring expression
+ */
+ public boolean hasReadAuthority() {
+ return
isAdminOrHasAnyAuthority(DefaultPrivilege.Constants.PRIVILEGE_READ_ADAPTER_VALUE);
+ }
+
+ /**
+ * required by Spring expression
+ */
+ public boolean hasWriteAuthority() {
+ return
isAdminOrHasAnyAuthority(DefaultPrivilege.Constants.PRIVILEGE_WRITE_ADAPTER_VALUE);
+ }
}
diff --git
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineMonitoring.java
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineMonitoring.java
index f772754391..135bf5f938 100644
---
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineMonitoring.java
+++
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineMonitoring.java
@@ -19,11 +19,13 @@ package org.apache.streampipes.rest.impl;
import
org.apache.streampipes.manager.monitoring.pipeline.ExtensionsLogProvider;
import
org.apache.streampipes.manager.monitoring.pipeline.ExtensionsServiceLogExecutor;
+import org.apache.streampipes.model.client.user.DefaultPrivilege;
import org.apache.streampipes.model.monitoring.SpLogEntry;
import org.apache.streampipes.model.monitoring.SpMetricsEntry;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -37,20 +39,43 @@ import java.util.Map;
@RequestMapping("/api/v2/pipeline-monitoring")
public class PipelineMonitoring extends AbstractMonitoringResource {
- @GetMapping(value = "/pipeline/{pipelineId}/logs", produces =
MediaType.APPLICATION_JSON_VALUE)
+ @GetMapping(
+ value = "/pipeline/{pipelineId}/logs",
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ @PreAuthorize("this.hasReadAuthority() and hasPermission('#pipelineId',
'READ')")
public ResponseEntity<Map<String, List<SpLogEntry>>> getLogInfoForPipeline(
- @PathVariable("pipelineId") String pipelineId) {
+ @PathVariable("pipelineId") String pipelineId
+ ) {
return
ok(ExtensionsLogProvider.INSTANCE.getLogInfosForPipeline(pipelineId));
}
- @GetMapping(value = "/pipeline/{pipelineId}/metrics", produces =
MediaType.APPLICATION_JSON_VALUE)
+ @GetMapping(
+ value = "/pipeline/{pipelineId}/metrics",
+ produces = MediaType.APPLICATION_JSON_VALUE
+ )
+ @PreAuthorize("this.hasReadAuthority() and hasPermission('#pipelineId',
'READ')")
public ResponseEntity<Map<String, SpMetricsEntry>> getMetricsInfoForPipeline(
@PathVariable("pipelineId") String pipelineId,
- @RequestParam(value = "forceUpdate", required = false, defaultValue =
"false") boolean forceUpdate) {
+ @RequestParam(value = "forceUpdate", required = false, defaultValue =
"false") boolean forceUpdate
+ ) {
if (forceUpdate) {
new ExtensionsServiceLogExecutor().triggerUpdate();
}
return
ok(ExtensionsLogProvider.INSTANCE.getMetricInfosForPipeline(pipelineId));
}
+ /**
+ * required by Spring expression
+ */
+ public boolean hasReadAuthority() {
+ return
isAdminOrHasAnyAuthority(DefaultPrivilege.Constants.PRIVILEGE_READ_PIPELINE_VALUE);
+ }
+
+ /**
+ * required by Spring expression
+ */
+ public boolean hasWriteAuthority() {
+ return
isAdminOrHasAnyAuthority(DefaultPrivilege.Constants.PRIVILEGE_WRITE_PIPELINE_VALUE);
+ }
}
diff --git
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineResource.java
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineResource.java
index d2dbdf5676..b897a9d599 100644
---
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineResource.java
+++
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/PipelineResource.java
@@ -25,6 +25,7 @@ import
org.apache.streampipes.manager.pipeline.PipelineManager;
import
org.apache.streampipes.manager.pipeline.compact.CompactPipelineManagement;
import org.apache.streampipes.manager.recommender.ElementRecommender;
import org.apache.streampipes.manager.storage.PipelineStorageService;
+import org.apache.streampipes.model.client.user.DefaultPrivilege;
import org.apache.streampipes.model.message.ErrorMessage;
import org.apache.streampipes.model.message.Message;
import org.apache.streampipes.model.message.Notification;
@@ -37,7 +38,6 @@ import
org.apache.streampipes.model.pipeline.PipelineElementRecommendationMessag
import org.apache.streampipes.model.pipeline.PipelineOperationStatus;
import org.apache.streampipes.model.pipeline.compact.CompactPipeline;
import
org.apache.streampipes.rest.core.base.impl.AbstractAuthGuardedRestResource;
-import org.apache.streampipes.rest.security.AuthConstants;
import org.apache.streampipes.rest.shared.exception.SpMessageException;
import org.apache.streampipes.rest.shared.exception.SpNotificationException;
import org.apache.streampipes.storage.management.StorageDispatcher;
@@ -95,7 +95,7 @@ public class PipelineResource extends
AbstractAuthGuardedRestResource {
mediaType = "application/json",
array = @ArraySchema(schema = @Schema(implementation =
Pipeline.class))
)})})
- @PreAuthorize(AuthConstants.HAS_READ_PIPELINE_PRIVILEGE)
+ @PreAuthorize("this.hasReadAuthority()")
@PostFilter("hasPermission(filterObject.pipelineId, 'READ')")
public List<Pipeline> get() {
return PipelineManager.getAllPipelines();
@@ -105,7 +105,7 @@ public class PipelineResource extends
AbstractAuthGuardedRestResource {
path = "{pipelineId}/status",
produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Get the pipeline status of a given pipeline", tags =
{"Pipeline"})
- @PreAuthorize(AuthConstants.HAS_READ_PIPELINE_PRIVILEGE)
+ @PreAuthorize("this.hasReadAuthority()")
public List<PipelineStatusMessage>
getPipelineStatus(@PathVariable("pipelineId") String pipelineId) {
return PipelineStatusManager.getPipelineStatus(pipelineId, 5);
}
@@ -114,15 +114,15 @@ public class PipelineResource extends
AbstractAuthGuardedRestResource {
path = "/{pipelineId}",
produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Delete a pipeline with a given id", tags =
{"Pipeline"})
- @PreAuthorize(AuthConstants.HAS_WRITE_PIPELINE_PRIVILEGE)
- public Message removeOwn(@PathVariable("pipelineId") String pipelineId) {
+ @PreAuthorize("this.hasWriteAuthority() and hasPermission('#pipelineId',
'WRITE')")
+ public Message delete(@PathVariable("pipelineId") String pipelineId) {
PipelineManager.deletePipeline(pipelineId);
return Notifications.success("Pipeline deleted");
}
@GetMapping(path = "/{pipelineId}", produces =
MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Get a specific pipeline with the given id", tags =
{"Pipeline"})
- @PreAuthorize(AuthConstants.HAS_READ_PIPELINE_PRIVILEGE)
+ @PreAuthorize("this.hasReadAuthority() and hasPermission('#pipelineId',
'READ')")
public ResponseEntity<Pipeline> getElement(@PathVariable("pipelineId")
String pipelineId) {
Pipeline foundPipeline = PipelineManager.getPipeline(pipelineId);
@@ -135,7 +135,7 @@ public class PipelineResource extends
AbstractAuthGuardedRestResource {
@GetMapping(path = "/{pipelineId}/start", produces =
MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Start the pipeline with the given id", tags =
{"Pipeline"})
- @PreAuthorize(AuthConstants.HAS_WRITE_PIPELINE_PRIVILEGE)
+ @PreAuthorize("this.hasWriteAuthority() and hasPermission('#pipelineId',
'WRITE')")
public ResponseEntity<?> start(@PathVariable("pipelineId") String
pipelineId) {
try {
PipelineOperationStatus status =
PipelineManager.startPipeline(pipelineId);
@@ -149,7 +149,7 @@ public class PipelineResource extends
AbstractAuthGuardedRestResource {
@GetMapping(path = "/{pipelineId}/stop", produces =
MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Stop the pipeline with the given id", tags =
{"Pipeline"})
- @PreAuthorize(AuthConstants.HAS_WRITE_PIPELINE_PRIVILEGE)
+ @PreAuthorize("this.hasWriteAuthority() and hasPermission('#pipelineId',
'WRITE')")
public ResponseEntity<?> stop(@PathVariable("pipelineId") String pipelineId,
@RequestParam(value = "forceStop",
defaultValue = "false") boolean forceStop) {
try {
@@ -166,7 +166,7 @@ public class PipelineResource extends
AbstractAuthGuardedRestResource {
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Store a new pipeline", tags = {"Pipeline"})
- @PreAuthorize(AuthConstants.HAS_WRITE_PIPELINE_PRIVILEGE)
+ @PreAuthorize("this.hasWriteAuthority()")
public ResponseEntity<SuccessMessage> addPipeline(@RequestBody Pipeline
pipeline) {
String pipelineId = PipelineManager.addPipeline(getAuthenticatedUserSid(),
pipeline);
@@ -180,7 +180,7 @@ public class PipelineResource extends
AbstractAuthGuardedRestResource {
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Convert a pipeline to the compact model", tags =
{"Pipeline"})
- @PreAuthorize(AuthConstants.HAS_WRITE_PIPELINE_PRIVILEGE)
+ @PreAuthorize("this.hasWriteAuthority()")
public ResponseEntity<CompactPipeline> convertToCompactPipeline(@RequestBody
Pipeline pipeline) {
return ok(compactPipelineManagement.convertPipeline(pipeline));
}
@@ -190,7 +190,7 @@ public class PipelineResource extends
AbstractAuthGuardedRestResource {
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
@Hidden
- @PreAuthorize(AuthConstants.HAS_WRITE_PIPELINE_PRIVILEGE)
+ @PreAuthorize("this.hasWriteAuthority()")
@PostAuthorize("hasPermission(returnObject, 'READ')")
public PipelineElementRecommendationMessage recommend(@RequestBody Pipeline
pipeline,
@PathVariable("recId")
String baseRecElement) {
@@ -221,7 +221,7 @@ public class PipelineResource extends
AbstractAuthGuardedRestResource {
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
@Hidden
- @PreAuthorize(AuthConstants.HAS_WRITE_PIPELINE_PRIVILEGE)
+ @PreAuthorize("this.hasWriteAuthority()")
public ResponseEntity<?> validatePipeline(@RequestBody Pipeline pipeline) {
try {
return ok(new PipelineVerificationHandlerV2(pipeline).verifyPipeline());
@@ -238,8 +238,8 @@ public class PipelineResource extends
AbstractAuthGuardedRestResource {
produces = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Update an existing pipeline", tags = {"Pipeline"})
- @PreAuthorize(AuthConstants.HAS_WRITE_PIPELINE_PRIVILEGE)
- public ResponseEntity<SuccessMessage>
overwritePipeline(@PathVariable("pipelineId") String pipelineId,
+ @PreAuthorize("this.hasWriteAuthority() and hasPermission('#pipelineId',
'WRITE')")
+ public ResponseEntity<SuccessMessage>
updatePipeline(@PathVariable("pipelineId") String pipelineId,
@RequestBody
Pipeline pipeline) {
Pipeline storedPipeline = getPipelineStorage().getElementById(pipelineId);
if (!storedPipeline.isRunning()) {
@@ -264,10 +264,23 @@ public class PipelineResource extends
AbstractAuthGuardedRestResource {
"Pipeline"}, responses = {@ApiResponse(content = {
@Content(mediaType = "application/json",
array = @ArraySchema(schema = @Schema(implementation =
Pipeline.class)))})})
- @PreAuthorize(AuthConstants.HAS_READ_PIPELINE_PRIVILEGE)
+ @PreAuthorize("this.hasReadAuthority()")
@PostFilter("hasPermission(filterObject.pipelineId, 'READ')")
public List<Pipeline>
getPipelinesContainingElement(@PathVariable("elementId") String elementId) {
return PipelineManager.getPipelinesContainingElements(elementId);
}
+ /**
+ * required by Spring expression
+ */
+ public boolean hasReadAuthority() {
+ return
isAdminOrHasAnyAuthority(DefaultPrivilege.Constants.PRIVILEGE_READ_PIPELINE_VALUE);
+ }
+
+ /**
+ * required by Spring expression
+ */
+ public boolean hasWriteAuthority() {
+ return
isAdminOrHasAnyAuthority(DefaultPrivilege.Constants.PRIVILEGE_WRITE_PIPELINE_VALUE);
+ }
}
diff --git
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/AbstractAdapterResource.java
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/AbstractAdapterResource.java
index c9623a4df1..dcdd800b6a 100644
---
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/AbstractAdapterResource.java
+++
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/AbstractAdapterResource.java
@@ -17,6 +17,7 @@
*/
package org.apache.streampipes.rest.impl.connect;
+import org.apache.streampipes.model.client.user.DefaultPrivilege;
import
org.apache.streampipes.rest.core.base.impl.AbstractAuthGuardedRestResource;
import java.util.function.Supplier;
@@ -28,4 +29,18 @@ public class AbstractAdapterResource<T> extends
AbstractAuthGuardedRestResource
public AbstractAdapterResource(Supplier<T> managementServiceSupplier) {
this.managementService = managementServiceSupplier.get();
}
+
+ /**
+ * required by Spring expression
+ */
+ public boolean hasReadAuthority() {
+ return
isAdminOrHasAnyAuthority(DefaultPrivilege.Constants.PRIVILEGE_READ_ADAPTER_VALUE);
+ }
+
+ /**
+ * required by Spring expression
+ */
+ public boolean hasWriteAuthority() {
+ return
isAdminOrHasAnyAuthority(DefaultPrivilege.Constants.PRIVILEGE_WRITE_ADAPTER_VALUE);
+ }
}
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 372769a792..b0a7b2c8e6 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
@@ -36,6 +36,7 @@ import org.apache.streampipes.model.util.ElementIdGenerator;
import org.apache.streampipes.resource.management.PermissionResourceManager;
import org.apache.streampipes.resource.management.SpResourceManager;
import org.apache.streampipes.rest.security.AuthConstants;
+import org.apache.streampipes.rest.security.SpPermissionEvaluator;
import org.apache.streampipes.rest.shared.constants.SpMediaType;
import org.apache.streampipes.storage.api.IPipelineStorage;
import org.apache.streampipes.storage.management.StorageDispatcher;
@@ -46,6 +47,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.DeleteMapping;
@@ -70,7 +72,7 @@ public class AdapterResource extends
AbstractAdapterResource<AdapterMasterManage
public AdapterResource() {
super(() -> new AdapterMasterManagement(
StorageDispatcher.INSTANCE.getNoSqlStore()
- .getAdapterInstanceStorage(),
+ .getAdapterInstanceStorage(),
new SpResourceManager().manageAdapters(),
new SpResourceManager().manageDataStreams(),
AdapterMetricsManager.INSTANCE.getAdapterMetrics()
@@ -78,7 +80,7 @@ public class AdapterResource extends
AbstractAdapterResource<AdapterMasterManage
}
@PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
- @PreAuthorize(AuthConstants.HAS_WRITE_ADAPTER_PRIVILEGE)
+ @PreAuthorize("this.hasWriteAuthority()")
public ResponseEntity<? extends Message> addAdapter(@RequestBody
AdapterDescription adapterDescription) {
var principalSid = getAuthenticatedUserSid();
var username = getAuthenticatedUsername();
@@ -102,14 +104,14 @@ public class AdapterResource extends
AbstractAdapterResource<AdapterMasterManage
SpMediaType.YAML,
SpMediaType.YML
})
- @PreAuthorize(AuthConstants.HAS_WRITE_ADAPTER_PRIVILEGE)
+ @PreAuthorize("this.hasWriteAuthority()")
public ResponseEntity<?> convertToCompactAdapter(@RequestBody
AdapterDescription adapterDescription)
throws Exception {
return ok(new
CompactAdapterManagement(List.of()).convertToCompactAdapter(adapterDescription));
}
@PutMapping(produces = MediaType.APPLICATION_JSON_VALUE, consumes =
MediaType.APPLICATION_JSON_VALUE)
- @PreAuthorize(AuthConstants.HAS_WRITE_ADAPTER_PRIVILEGE)
+ @PreAuthorize("this.hasWriteAuthority() and
hasPermission('#adapterDescription.elementId', 'WRITE')")
public ResponseEntity<? extends Message> updateAdapter(@RequestBody
AdapterDescription adapterDescription) {
var updateManager = new AdapterUpdateManagement(managementService);
try {
@@ -123,7 +125,7 @@ public class AdapterResource extends
AbstractAdapterResource<AdapterMasterManage
}
@PutMapping(path = "pipeline-migration-preflight", consumes =
MediaType.APPLICATION_JSON_VALUE,
- produces = MediaType.APPLICATION_JSON_VALUE)
+ produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize(AuthConstants.HAS_WRITE_ADAPTER_PRIVILEGE)
public ResponseEntity<List<PipelineUpdateInfo>>
performPipelineMigrationPreflight(
@RequestBody AdapterDescription adapterDescription
@@ -135,60 +137,87 @@ public class AdapterResource extends
AbstractAdapterResource<AdapterMasterManage
}
@GetMapping(path = "/{id}", produces = {MediaType.APPLICATION_JSON_VALUE,
SpMediaType.YAML, SpMediaType.YML})
- @PreAuthorize(AuthConstants.HAS_READ_ADAPTER_PRIVILEGE)
- public ResponseEntity<?> getAdapter(@PathVariable("id") String adapterId,
- @RequestParam(value = "output",
- defaultValue = "full",
- required = false) String outputMode)
{
+ @PreAuthorize("this.hasReadAuthority()")
+ public ResponseEntity<?> getAdapter(
+ @PathVariable("id") String elementId,
+ @RequestParam(value = "output",
+ defaultValue = "full",
+ required = false) String outputMode
+ ) {
try {
- AdapterDescription adapterDescription = getAdapterDescription(adapterId);
+ var adapterDescription = getAdapterDescription(elementId);
+
+ // This check is done here because the adapter permission is checked
based on the corresponding data stream
+ // and not based on the element id
+ if (!checkAdapterReadPermission(adapterDescription)) {
+ LOG.error("User is not allowed to read adapter {}", elementId);
+ return ResponseEntity.status(HttpStatus.SC_UNAUTHORIZED)
+ .build();
+ }
+
if (outputMode.equalsIgnoreCase("compact")) {
return ok(toCompactAdapterDescription(adapterDescription));
} else {
return ok(adapterDescription);
}
} catch (AdapterException e) {
- LOG.error("Error while getting adapter with id {}", adapterId, e);
+ LOG.error("Error while getting adapter with id {}", elementId, e);
return fail();
} catch (Exception e) {
- LOG.error("Error while transforming adapter {}", adapterId, e);
+ LOG.error("Error while transforming adapter {}", elementId, e);
return fail();
}
}
+ /**
+ * Checks if the current user has the permission to read the adapter
+ */
+ private boolean checkAdapterReadPermission(AdapterDescription
adapterDescription) {
+ var spPermissionEvaluator = new SpPermissionEvaluator();
+ var authentication = SecurityContextHolder.getContext()
+ .getAuthentication();
+ return spPermissionEvaluator.hasPermission(
+ authentication,
+ adapterDescription.getCorrespondingDataStreamElementId(),
+ "READ"
+ );
+ }
+
@PostMapping(path = "/{id}/stop", produces =
MediaType.APPLICATION_JSON_VALUE)
- @PreAuthorize(AuthConstants.HAS_WRITE_ADAPTER_PRIVILEGE)
- public ResponseEntity<?> stopAdapter(@PathVariable("id") String adapterId) {
+ @PreAuthorize("this.hasWriteAuthority() and hasPermission('#elementId',
'WRITE')")
+ public ResponseEntity<?> stopAdapter(@PathVariable("id") String elementId) {
try {
- managementService.stopStreamAdapter(adapterId);
+ managementService.stopStreamAdapter(elementId);
return ok(Notifications.success("Adapter started"));
} catch (AdapterException e) {
- LOG.error("Could not stop adapter with id {}", adapterId, e);
+ LOG.error("Could not stop adapter with id {}", elementId, e);
return serverError(SpLogMessage.from(e));
}
}
@PostMapping(path = "/{id}/start", produces =
MediaType.APPLICATION_JSON_VALUE)
- @PreAuthorize(AuthConstants.HAS_WRITE_ADAPTER_PRIVILEGE)
- public ResponseEntity<?> startAdapter(@PathVariable("id") String adapterId) {
+ @PreAuthorize("this.hasWriteAuthority() and hasPermission('#elementId',
'WRITE')")
+ public ResponseEntity<?> startAdapter(@PathVariable("id") String elementId) {
try {
- managementService.startStreamAdapter(adapterId);
+ managementService.startStreamAdapter(elementId);
return ok(Notifications.success("Adapter stopped"));
} catch (AdapterException e) {
- LOG.error("Could not start adapter with id {}", adapterId, e);
+ LOG.error("Could not start adapter with id {}", elementId, e);
return serverError(SpLogMessage.from(e));
}
}
@DeleteMapping(path = "/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
- @PreAuthorize(AuthConstants.HAS_WRITE_ADAPTER_PRIVILEGE)
- public ResponseEntity<?> deleteAdapter(@PathVariable("id") String elementId,
- @RequestParam(value =
"deleteAssociatedPipelines", defaultValue = "false")
- boolean deleteAssociatedPipelines) {
+ @PreAuthorize("this.hasWriteAuthority() and hasPermission('#elementId',
'WRITE')")
+ public ResponseEntity<?> deleteAdapter(
+ @PathVariable("id") String elementId,
+ @RequestParam(value = "deleteAssociatedPipelines", defaultValue =
"false")
+ boolean deleteAssociatedPipelines
+ ) {
List<String> pipelinesUsingAdapter = getPipelinesUsingAdapter(elementId);
IPipelineStorage pipelineStorageAPI =
StorageDispatcher.INSTANCE.getNoSqlStore()
- .getPipelineStorageAPI();
+
.getPipelineStorageAPI();
if (pipelinesUsingAdapter.isEmpty()) {
try {
@@ -202,22 +231,40 @@ public class AdapterResource extends
AbstractAdapterResource<AdapterMasterManage
List<String> namesOfPipelinesUsingAdapter = pipelinesUsingAdapter
.stream()
.map(pipelineId -> pipelineStorageAPI.getElementById(
- pipelineId)
- .getName())
+ pipelineId)
+ .getName())
.collect(Collectors.toList());
return ResponseEntity.status(HttpStatus.SC_CONFLICT)
- .body(String.join(", ", namesOfPipelinesUsingAdapter));
+ .body(String.join(", ",
namesOfPipelinesUsingAdapter));
} else {
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.getElementById(pipelineId).getName()).collect(Collectors.toList());
- boolean isAdmin =
SecurityContextHolder.getContext().getAuthentication().getAuthorities().stream()
- .anyMatch(r -> r.getAuthority().equals(
- DefaultRole.ROLE_ADMIN.name()));
+ 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.getElementById(
+ pipelineId)
+ .getName())
+ .collect(Collectors.toList());
+ boolean isAdmin = SecurityContextHolder.getContext()
+ .getAuthentication()
+ .getAuthorities()
+ .stream()
+ .anyMatch(r -> r.getAuthority()
+ .equals(
+
DefaultRole.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()) {
@@ -228,34 +275,31 @@ public class AdapterResource extends
AbstractAdapterResource<AdapterMasterManage
}
managementService.deleteAdapter(elementId);
return ok(Notifications.success("Adapter with id: " + elementId
- + " and all pipelines using the adapter are deleted."));
+ + " 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);
+ 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 ResponseEntity.status(HttpStatus.SC_CONFLICT)
- .body(String.join(", ", namesOfPipelinesNotOwnedByUser));
+ .body(String.join(", ",
namesOfPipelinesNotOwnedByUser));
}
}
}
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
- @PreAuthorize(AuthConstants.HAS_READ_ADAPTER_PRIVILEGE)
- public ResponseEntity<?> getAllAdapters() {
- try {
- return ok(managementService.getAllAdapterInstances());
- } catch (AdapterException e) {
- LOG.error("Error while getting all adapters", e);
- return ResponseEntity.status(500)
- .build();
- }
+ @PreAuthorize("this.hasReadAuthority()")
+ @PostFilter("hasPermission(filterObject.correspondingDataStreamElementId,
'READ')")
+ public List<AdapterDescription> getAllAdapters() {
+ return managementService.getAllAdapterInstances();
}
- private AdapterDescription getAdapterDescription(String adapterId) throws
AdapterException {
- return managementService.getAdapter(adapterId);
+ private AdapterDescription getAdapterDescription(String elementId) throws
AdapterException {
+ return managementService.getAdapter(elementId);
}
private CompactAdapter toCompactAdapterDescription(AdapterDescription
adapterDescription) throws Exception {
@@ -264,8 +308,8 @@ public class AdapterResource extends
AbstractAdapterResource<AdapterMasterManage
private List<String> getPipelinesUsingAdapter(String adapterId) {
return StorageDispatcher.INSTANCE.getNoSqlStore()
- .getPipelineStorageAPI()
- .getPipelinesUsingAdapter(adapterId);
+ .getPipelineStorageAPI()
+ .getPipelinesUsingAdapter(adapterId);
}
}
diff --git
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/CompactAdapterResource.java
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/CompactAdapterResource.java
index 0e51f00927..89912db3d8 100644
---
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/CompactAdapterResource.java
+++
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/CompactAdapterResource.java
@@ -30,7 +30,6 @@ import
org.apache.streampipes.model.connect.adapter.AdapterDescription;
import org.apache.streampipes.model.connect.adapter.compact.CompactAdapter;
import org.apache.streampipes.model.message.Notifications;
import org.apache.streampipes.resource.management.SpResourceManager;
-import org.apache.streampipes.rest.security.AuthConstants;
import org.apache.streampipes.rest.shared.constants.SpMediaType;
import org.apache.streampipes.rest.shared.exception.BadRequestException;
import org.apache.streampipes.storage.management.StorageDispatcher;
@@ -75,7 +74,7 @@ public class CompactAdapterResource extends
AbstractAdapterResource<AdapterMaste
SpMediaType.YAML
}
)
- @PreAuthorize(AuthConstants.HAS_WRITE_ADAPTER_PRIVILEGE)
+ @PreAuthorize("this.hasWriteAuthority()")
public ResponseEntity<?> addAdapterCompact(
@RequestBody CompactAdapter compactAdapter
) throws Exception {
@@ -129,7 +128,7 @@ public class CompactAdapterResource extends
AbstractAdapterResource<AdapterMaste
"application/yml"
}
)
- @PreAuthorize(AuthConstants.HAS_WRITE_ADAPTER_PRIVILEGE)
+ @PreAuthorize("this.hasWriteAuthority() and hasPermission('#elementId',
'WRITE')")
public ResponseEntity<?> updateAdapterCompact(
@PathVariable("id") String elementId,
@RequestBody CompactAdapter compactAdapter
@@ -164,4 +163,5 @@ public class CompactAdapterResource extends
AbstractAdapterResource<AdapterMaste
var generators = adapterGenerationSteps.getGenerators();
return new
CompactAdapterManagement(generators).convertToAdapterDescription(compactAdapter,
existingAdapter);
}
+
}
diff --git
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/DescriptionResource.java
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/DescriptionResource.java
index 7d8f562034..d26eaf951a 100644
---
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/DescriptionResource.java
+++
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/DescriptionResource.java
@@ -31,6 +31,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -53,6 +54,7 @@ public class DescriptionResource extends
AbstractAdapterResource<DescriptionMana
}
@GetMapping(path = "/adapters", produces = MediaType.APPLICATION_JSON_VALUE)
+ @PreAuthorize("this.hasReadAuthority()")
public ResponseEntity<List<AdapterDescription>> getAdapters() {
List<AdapterDescription> result = managementService.getAdapters();
@@ -60,6 +62,7 @@ public class DescriptionResource extends
AbstractAdapterResource<DescriptionMana
}
@GetMapping(path = "/{id}/assets", produces = "application/zip")
+ @PreAuthorize("this.hasReadAuthority()")
public ResponseEntity<?> getAdapterAssets(@PathVariable("id") String id) {
try {
String result = null;
@@ -87,6 +90,7 @@ public class DescriptionResource extends
AbstractAdapterResource<DescriptionMana
}
@GetMapping(path = "/{id}/assets/icon", produces = "image/png")
+ @PreAuthorize("this.hasReadAuthority()")
public ResponseEntity<?> getAdapterIconAsset(@PathVariable("id") String id) {
try {
@@ -115,6 +119,7 @@ public class DescriptionResource extends
AbstractAdapterResource<DescriptionMana
}
@GetMapping(path = "/{id}/assets/documentation", produces =
MediaType.TEXT_PLAIN_VALUE)
+ @PreAuthorize("this.hasReadAuthority()")
public ResponseEntity<?> getAdapterDocumentationAsset(@PathVariable("id")
String id) {
try {
String result = null;
@@ -142,6 +147,7 @@ public class DescriptionResource extends
AbstractAdapterResource<DescriptionMana
}
@DeleteMapping(path = "{adapterId}")
+ @PreAuthorize("this.hasWriteAuthority()")
public ResponseEntity<?> deleteAdapter(@PathVariable("adapterId") String
adapterId) {
try {
this.managementService.deleteAdapterDescription(adapterId);
diff --git
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/GuessResource.java
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/GuessResource.java
index ddb51a9a92..f49dfaa7c9 100644
---
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/GuessResource.java
+++
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/GuessResource.java
@@ -22,6 +22,7 @@ import
org.apache.streampipes.commons.exceptions.NoServiceEndpointsAvailableExce
import org.apache.streampipes.commons.exceptions.connect.ParseException;
import org.apache.streampipes.connect.management.management.GuessManagement;
import
org.apache.streampipes.extensions.api.connect.exception.WorkerAdapterException;
+import org.apache.streampipes.model.client.user.DefaultPrivilege;
import org.apache.streampipes.model.connect.adapter.AdapterDescription;
import org.apache.streampipes.model.connect.guess.AdapterEventPreview;
import org.apache.streampipes.model.connect.guess.GuessSchema;
@@ -32,6 +33,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -53,6 +55,7 @@ public class GuessResource extends
AbstractAdapterResource<GuessManagement> {
path = "/schema",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
+ @PreAuthorize("this.hasWriteAuthority()")
public ResponseEntity<?> guessSchema(@RequestBody AdapterDescription
adapterDescription) {
try {
@@ -74,6 +77,7 @@ public class GuessResource extends
AbstractAdapterResource<GuessManagement> {
path = "/schema/preview",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
+ @PreAuthorize("this.hasWriteAuthority()")
public ResponseEntity<?> getAdapterEventPreview(@RequestBody
AdapterEventPreview previewRequest) {
try {
return ok(managementService.performAdapterEventPreview(previewRequest));
@@ -81,5 +85,13 @@ public class GuessResource extends
AbstractAdapterResource<GuessManagement> {
return badRequest();
}
}
+
+ /**
+ * required by Spring expression
+ */
+ public boolean hasWriteAuthority() {
+ return
isAdminOrHasAnyAuthority(DefaultPrivilege.Constants.PRIVILEGE_WRITE_ADAPTER_VALUE);
+ }
+
}
diff --git
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/RuntimeResolvableResource.java
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/RuntimeResolvableResource.java
index ce66dbeed8..cf30a41758 100644
---
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/RuntimeResolvableResource.java
+++
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/RuntimeResolvableResource.java
@@ -38,6 +38,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -67,6 +68,7 @@ public class RuntimeResolvableResource extends
AbstractAdapterResource<WorkerAdm
path = "{id}/configurations",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
+ @PreAuthorize("this.hasWriteAuthority()")
public ResponseEntity<?> fetchConfigurations(@PathVariable("id") String
appId,
@RequestBody
RuntimeOptionsRequest runtimeOptionsRequest) {
diff --git
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/UnitResource.java
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/UnitResource.java
index 1efb585e82..db2edcdd6c 100644
---
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/UnitResource.java
+++
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/impl/connect/UnitResource.java
@@ -26,6 +26,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
@@ -48,6 +49,7 @@ public class UnitResource extends
AbstractAdapterResource<UnitMasterManagement>
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
+ @PreAuthorize("this.hasReadAuthority()")
public ResponseEntity<?> getFittingUnits(@RequestBody UnitDescription
unitDescription) {
try {
String resultingJson =
managementService.getFittingUnits(unitDescription);
@@ -59,8 +61,9 @@ public class UnitResource extends
AbstractAdapterResource<UnitMasterManagement>
}
@GetMapping(path = "/units",
- produces = MediaType.APPLICATION_JSON_VALUE)
- public ResponseEntity<List<UnitDescription>> getAllUnits(){
+ produces = MediaType.APPLICATION_JSON_VALUE)
+ @PreAuthorize("this.hasReadAuthority()")
+ public ResponseEntity<List<UnitDescription>> getAllUnits() {
List<UnitDescription> unitDescriptions =
managementService.getAllUnitDescriptions();
return ok(unitDescriptions);
}
diff --git
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/security/SpPermissionEvaluator.java
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/security/SpPermissionEvaluator.java
index 08ee86cc46..86c0a74d80 100644
---
a/streampipes-rest/src/main/java/org/apache/streampipes/rest/security/SpPermissionEvaluator.java
+++
b/streampipes-rest/src/main/java/org/apache/streampipes/rest/security/SpPermissionEvaluator.java
@@ -35,39 +35,71 @@ import java.util.function.Predicate;
@Configuration
public class SpPermissionEvaluator implements PermissionEvaluator {
+ /**
+ * Evaluates whether the user has the necessary permissions for a given
resource.
+ *
+ * @param authentication The authentication object containing the user's
credentials.
+ * @param targetDomainObject The resource being accessed, which can be an
instance of
+ * PipelineElementRecommendationMessage or a
String representing the resource ID.
+ * @param permission Is not used in this implementation.
+ * @return true if the user has the necessary permissions, false otherwise.
+ */
@Override
- public boolean hasPermission(Authentication auth, Object o, Object
permission) {
- PrincipalUserDetails<?> userDetails = getUserDetails(auth);
- if (o instanceof PipelineElementRecommendationMessage) {
- return isAdmin(userDetails) || filterRecommendation(auth,
(PipelineElementRecommendationMessage) o);
+ public boolean hasPermission(
+ Authentication authentication,
+ Object targetDomainObject,
+ Object permission
+ ) {
+ PrincipalUserDetails<?> userDetails = getUserDetails(authentication);
+ if (targetDomainObject instanceof PipelineElementRecommendationMessage) {
+ return isAdmin(userDetails) || filterRecommendation(
+ authentication,
+ (PipelineElementRecommendationMessage) targetDomainObject
+ );
} else {
- String objectInstanceId = (String) o;
+ String objectInstanceId = (String) targetDomainObject;
if (isAdmin(userDetails)) {
return true;
}
- return hasPermission(auth, objectInstanceId);
+ return hasPermission(authentication, objectInstanceId);
}
}
- private boolean filterRecommendation(Authentication auth,
PipelineElementRecommendationMessage message) {
- Predicate<PipelineElementRecommendation> isForbidden = r ->
!hasPermission(auth, r.getElementId());
- message.getPossibleElements().removeIf(isForbidden);
-
- return true;
- }
-
+ /**
+ * Evaluates whether the user has the necessary permissions for a given
resource.
+ *
+ * @param authentication The authentication object containing the user's
credentials.
+ * @param targetId The ID of the resource being accessed.
+ * @param targetType Is not used in this implementation.
+ * @param permission Is not used in this implementation.
+ * @return true if the user has the necessary permissions, false otherwise.
+ */
@Override
- public boolean hasPermission(Authentication auth, Serializable serializable,
String s, Object permission) {
- PrincipalUserDetails<?> userDetails = getUserDetails(auth);
+ public boolean hasPermission(
+ Authentication authentication,
+ Serializable targetId,
+ String targetType,
+ Object permission
+ ) {
+ PrincipalUserDetails<?> userDetails = getUserDetails(authentication);
if (isAdmin(userDetails)) {
return true;
}
- return hasPermission(auth, serializable.toString());
+ return hasPermission(authentication, targetId.toString());
+ }
+
+ private boolean filterRecommendation(Authentication auth,
PipelineElementRecommendationMessage message) {
+ Predicate<PipelineElementRecommendation> isForbidden = r ->
!hasPermission(auth, r.getElementId());
+ message.getPossibleElements()
+ .removeIf(isForbidden);
+
+ return true;
}
private boolean hasPermission(Authentication auth, String objectInstanceId) {
return isPublicElement(objectInstanceId)
- ||
getUserDetails(auth).getAllObjectPermissions().contains(objectInstanceId);
+ || getUserDetails(auth).getAllObjectPermissions()
+ .contains(objectInstanceId);
}
private PrincipalUserDetails<?> getUserDetails(Authentication
authentication) {
@@ -76,14 +108,18 @@ public class SpPermissionEvaluator implements
PermissionEvaluator {
private boolean isPublicElement(String objectInstanceId) {
List<Permission> permissions =
-
StorageDispatcher.INSTANCE.getNoSqlStore().getPermissionStorage().getUserPermissionsForObject(objectInstanceId);
- return permissions.size() > 0 && permissions.get(0).isPublicElement();
+ StorageDispatcher.INSTANCE.getNoSqlStore()
+ .getPermissionStorage()
+
.getUserPermissionsForObject(objectInstanceId);
+ return permissions.size() > 0 && permissions.get(0)
+ .isPublicElement();
}
private boolean isAdmin(PrincipalUserDetails<?> userDetails) {
return userDetails
.getAuthorities()
.stream()
- .anyMatch(a ->
a.getAuthority().equals(DefaultRole.Constants.ROLE_ADMIN_VALUE));
+ .anyMatch(a -> a.getAuthority()
+ .equals(DefaultRole.Constants.ROLE_ADMIN_VALUE));
}
}
diff --git a/ui/cypress/support/utils/GeneralUtils.ts
b/ui/cypress/support/utils/GeneralUtils.ts
index 8db505e32b..9b0186b1f6 100644
--- a/ui/cypress/support/utils/GeneralUtils.ts
+++ b/ui/cypress/support/utils/GeneralUtils.ts
@@ -20,4 +20,11 @@ export class GeneralUtils {
public static tab(identifier: string) {
return cy.dataCy(`tab-${identifier}`).click();
}
+
+ public static validateAmountOfNavigationIcons(expected: number) {
+ cy.dataCy('navigation-icon', { timeout: 10000 }).should(
+ 'have.length',
+ expected,
+ );
+ }
}
diff --git a/ui/cypress/support/utils/UserUtils.ts
b/ui/cypress/support/utils/UserUtils.ts
index b91f046f07..3f966450db 100644
--- a/ui/cypress/support/utils/UserUtils.ts
+++ b/ui/cypress/support/utils/UserUtils.ts
@@ -62,6 +62,25 @@ export class UserUtils {
cy.dataCy('sp-element-edit-user-save').click();
}
+ /**
+ * Create a new user with the specified roles and a default password to
the system.
+ *
+ * @param name - The name of the user to be added.
+ * @param roles - The roles to be assigned to the new user.
+ */
+ public static createUser(name: string, ...roles: UserRole[]): User {
+ const userBuilder =
UserBuilder.create(`${name}@streampipes.apache.org`)
+ .setName(name)
+ .setPassword('default');
+
+ roles.forEach(role => userBuilder.addRole(role));
+
+ const user = userBuilder.build();
+
+ this.addUser(user);
+ return user;
+ }
+
public static switchUser(user: User) {
cy.logout();
cy.visit('#/login');
diff --git a/ui/cypress/support/utils/connect/ConnectUtils.ts
b/ui/cypress/support/utils/connect/ConnectUtils.ts
index 0a0eae22c1..52d2ec6c88 100644
--- a/ui/cypress/support/utils/connect/ConnectUtils.ts
+++ b/ui/cypress/support/utils/connect/ConnectUtils.ts
@@ -440,6 +440,13 @@ export class ConnectUtils {
public static checkAmountOfAdapters(amount: number) {
ConnectUtils.goToConnect();
- ConnectBtns.deleteAdapter().should('have.length', amount);
+ if (amount === 0) {
+ // The wait is needed because the default value is the
no-table-entries element.
+ // It must be waited till the data is loaded. Once a better
solution is found, this can be removed.
+ cy.wait(1000);
+ cy.dataCy('no-table-entries').should('be.visible');
+ } else {
+ ConnectBtns.deleteAdapter().should('have.length', amount);
+ }
}
}
diff --git a/ui/cypress/support/utils/pipeline/PipelineBtns.ts
b/ui/cypress/support/utils/pipeline/PipelineBtns.ts
index 6ace82220f..faece471a5 100644
--- a/ui/cypress/support/utils/pipeline/PipelineBtns.ts
+++ b/ui/cypress/support/utils/pipeline/PipelineBtns.ts
@@ -17,6 +17,14 @@
*/
export class PipelineBtns {
+ public static statusPipeline() {
+ return cy.dataCy('status-pipeline-green', { timeout: 10000 });
+ }
+
+ public static stopPipeline() {
+ return cy.dataCy('stop-pipeline-button', { timeout: 10000 });
+ }
+
public static deletePipeline() {
return cy.dataCy('delete-pipeline', { timeout: 10000 });
}
diff --git a/ui/cypress/support/utils/pipeline/PipelineUtils.ts
b/ui/cypress/support/utils/pipeline/PipelineUtils.ts
index 0fa9ce4443..2b50f512c0 100644
--- a/ui/cypress/support/utils/pipeline/PipelineUtils.ts
+++ b/ui/cypress/support/utils/pipeline/PipelineUtils.ts
@@ -21,6 +21,9 @@ import { StaticPropertyUtils } from
'../userInput/StaticPropertyUtils';
import { OutputStrategyUtils } from '../OutputStrategyUtils';
import { PipelineElementInput } from '../../model/PipelineElementInput';
import { PipelineBtns } from './PipelineBtns';
+import { ConnectUtils } from '../connect/ConnectUtils';
+import { PipelineBuilder } from '../../builder/PipelineBuilder';
+import { PipelineElementBuilder } from '../../builder/PipelineElementBuilder';
export class PipelineUtils {
public static addPipeline(pipelineInput: PipelineInput) {
@@ -33,6 +36,32 @@ export class PipelineUtils {
PipelineUtils.startPipeline(pipelineInput);
}
+ /**
+ * This method adds a sample adapter and pipeline
+ */
+ public static addSampleAdapterAndPipeline() {
+ const adapterName = 'simulator';
+
+ ConnectUtils.addMachineDataSimulator(adapterName);
+
+ const pipelineInput = PipelineBuilder.create('Pipeline Test')
+ .addSource(adapterName)
+ .addProcessingElement(
+ PipelineElementBuilder.create('field_renamer')
+ .addInput('drop-down', 'convert-property', 'timestamp')
+ .addInput('input', 'field-name', 't')
+ .build(),
+ )
+ .addSink(
+ PipelineElementBuilder.create('data_lake')
+ .addInput('input', 'db_measurement', 'demo')
+ .build(),
+ )
+ .build();
+
+ PipelineUtils.addPipeline(pipelineInput);
+ }
+
public static editPipeline() {
cy.dataCy('modify-pipeline-btn').first().click();
}
@@ -133,7 +162,15 @@ export class PipelineUtils {
public static checkAmountOfPipelinesPipeline(amount: number) {
PipelineUtils.goToPipelines();
- PipelineBtns.deletePipeline().should('have.length', amount);
+
+ if (amount === 0) {
+ // The wait is needed because the default value is the
no-table-entries element.
+ // It must be waited till the data is loaded. Once a better
solution is found, this can be removed.
+ cy.wait(1000);
+ cy.dataCy('no-table-entries').should('be.visible');
+ } else {
+ PipelineBtns.statusPipeline().should('have.length', amount);
+ }
}
public static deletePipeline() {
diff --git a/ui/cypress/support/utils/pipeline/PipelineBtns.ts
b/ui/cypress/support/utils/user/PermissionUtils.ts
similarity index 52%
copy from ui/cypress/support/utils/pipeline/PipelineBtns.ts
copy to ui/cypress/support/utils/user/PermissionUtils.ts
index 6ace82220f..9907c56acc 100644
--- a/ui/cypress/support/utils/pipeline/PipelineBtns.ts
+++ b/ui/cypress/support/utils/user/PermissionUtils.ts
@@ -16,8 +16,29 @@
*
*/
-export class PipelineBtns {
- public static deletePipeline() {
- return cy.dataCy('delete-pipeline', { timeout: 10000 });
+import { StaticPropertyUtils } from '../userInput/StaticPropertyUtils';
+
+export class PermissionUtils {
+ public static openManagePermissions() {
+ cy.dataCy('open-manage-permissions').click();
+ }
+
+ public static markElementAsPublic() {
+ PermissionUtils.openManagePermissions();
+ StaticPropertyUtils.clickCheckbox('permission-public-element');
+ PermissionUtils.save();
+ }
+
+ public static authorizeUser(email: string) {
+ PermissionUtils.openManagePermissions();
+
+ cy.dataCy('authorized-user').type(email);
+ cy.get(`[data-cy="user-option-${email}"]`).click();
+
+ PermissionUtils.save();
+ }
+
+ public static save() {
+ cy.dataCy('sp-manage-permissions-save').click();
}
}
diff --git a/ui/cypress/support/utils/userInput/StaticPropertyUtils.ts
b/ui/cypress/support/utils/userInput/StaticPropertyUtils.ts
index 34b84673f3..acc1a5eaf4 100644
--- a/ui/cypress/support/utils/userInput/StaticPropertyUtils.ts
+++ b/ui/cypress/support/utils/userInput/StaticPropertyUtils.ts
@@ -24,7 +24,7 @@ export class StaticPropertyUtils {
// Configure Properties
configs.forEach(config => {
if (config.type === 'checkbox') {
- this.clickCheckbox(config);
+ this.clickCheckbox(config.selector);
} else if (config.type === 'button') {
cy.dataCy(config.selector, { timeout: 2000 }).click();
} else if (config.type === 'drop-down') {
@@ -65,8 +65,12 @@ export class StaticPropertyUtils {
});
}
- private static clickCheckbox(input: UserInput) {
- this.clickSelectionInput(input.selector, '.mdc-checkbox');
+ /**
+ * This method can be used to check a mat checkbox
+ * @param selector
+ */
+ public static clickCheckbox(selector: string) {
+ this.clickSelectionInput(selector, '.mdc-checkbox');
}
private static clickRadio(input: UserInput) {
diff --git
a/ui/cypress/tests/connect/deleteAdapterWithMultipleUsers.smoke.spec.ts
b/ui/cypress/tests/connect/deleteAdapterWithMultipleUsers.smoke.spec.ts
deleted file mode 100644
index a11332ac9e..0000000000
--- a/ui/cypress/tests/connect/deleteAdapterWithMultipleUsers.smoke.spec.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * 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.
- *
- */
-
-import { ConnectUtils } from '../../support/utils/connect/ConnectUtils';
-import { PipelineBuilder } from '../../support/builder/PipelineBuilder';
-import { PipelineElementBuilder } from
'../../support/builder/PipelineElementBuilder';
-import { UserUtils } from '../../support/utils/UserUtils';
-import { PipelineUtils } from '../../support/utils/pipeline/PipelineUtils';
-
-const adapterName = 'simulator';
-
-const pipelineInput = PipelineBuilder.create('Pipeline Test')
- .addSource(adapterName)
- .addProcessingElement(
- PipelineElementBuilder.create('field_renamer')
- .addInput('drop-down', 'convert-property', 'timestamp')
- .addInput('input', 'field-name', 't')
- .build(),
- )
- .addSink(
- PipelineElementBuilder.create('data_lake')
- .addInput('input', 'db_measurement', 'demo')
- .build(),
- )
- .build();
-
-describe('Test Enhanced Adapter Deletion', () => {
- beforeEach('Setup Test', () => {
- cy.initStreamPipesTest();
- UserUtils.addUser(UserUtils.userWithAdapterAndPipelineAdminRights);
- });
-
- it('Test Delete Adapter and Associated Pipelines', () => {
- ConnectUtils.addMachineDataSimulator(adapterName, false);
- PipelineUtils.addPipeline(pipelineInput);
-
- ConnectUtils.deleteAdapterAndAssociatedPipelines();
- });
-
- it('Test Admin Should Be Able to Delete Adapter and Not Owned Associated
Pipelines', () => {
- // Let the user create the adapter and the pipeline
- ConnectUtils.addMachineDataSimulator(adapterName, false);
- PipelineUtils.addPipeline(pipelineInput);
-
- // Then let the admin delete them
- UserUtils.switchUser(UserUtils.adminUser);
- ConnectUtils.deleteAdapterAndAssociatedPipelines(true);
- });
-
- it('Test Delete Adapter and Associated Pipelines Permission Denied', () =>
{
- // Let the admin create the adapter and the pipeline
- UserUtils.switchUser(UserUtils.adminUser);
- ConnectUtils.addMachineDataSimulator(adapterName, false);
- PipelineUtils.addPipeline(pipelineInput);
-
- // Then the user shouldn't be able to delete them
- UserUtils.switchUser(UserUtils.userWithAdapterAndPipelineAdminRights);
- ConnectUtils.deleteAdapterAndAssociatedPipelinesPermissionDenied();
- });
-});
diff --git a/ui/cypress/tests/connect/enhancedAdapterDeletion.smoke.spec.ts
b/ui/cypress/tests/connect/enhancedAdapterDeletion.smoke.spec.ts
new file mode 100644
index 0000000000..e10fd81873
--- /dev/null
+++ b/ui/cypress/tests/connect/enhancedAdapterDeletion.smoke.spec.ts
@@ -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.
+ *
+ */
+
+import { ConnectUtils } from '../../support/utils/connect/ConnectUtils';
+import { UserUtils } from '../../support/utils/UserUtils';
+import { PipelineUtils } from '../../support/utils/pipeline/PipelineUtils';
+
+describe('Test Enhanced Adapter Deletion', () => {
+ beforeEach('Setup Test', () => {
+ cy.initStreamPipesTest();
+ UserUtils.addUser(UserUtils.userWithAdapterAndPipelineAdminRights);
+ });
+
+ it('Test Delete Adapter and Associated Pipelines', () => {
+ PipelineUtils.addSampleAdapterAndPipeline();
+
+ ConnectUtils.deleteAdapterAndAssociatedPipelines();
+ });
+
+ it('Test Admin Should Be Able to Delete Adapter and Not Owned Associated
Pipelines', () => {
+ // Let the user create the adapter and the pipeline
+ PipelineUtils.addSampleAdapterAndPipeline();
+
+ // Then let the admin delete them
+ UserUtils.switchUser(UserUtils.adminUser);
+ ConnectUtils.deleteAdapterAndAssociatedPipelines(true);
+ });
+});
diff --git a/ui/cypress/tests/datalake/timeRangeSelectors.spec.ts
b/ui/cypress/tests/datalake/timeRangeSelectors.spec.ts
index 21a1604fb3..71f2356ed9 100644
--- a/ui/cypress/tests/datalake/timeRangeSelectors.spec.ts
+++ b/ui/cypress/tests/datalake/timeRangeSelectors.spec.ts
@@ -123,7 +123,10 @@ function getLocalizedDateString(date: Date) {
}
function getLocalizedTimeString(date: Date) {
- return date.toLocaleTimeString().slice(0, 8);
+ const hours = String(date.getHours()).padStart(2, '0');
+ const minutes = String(date.getMinutes()).padStart(2, '0');
+ const seconds = String(date.getSeconds()).padStart(2, '0');
+ return `${hours}:${minutes}:${seconds}`;
}
function parseTimeStringToSeconds(timeString: string) {
diff --git a/ui/cypress/tests/pipeline/multiUser/pipelineMultiUserSupport.ts
b/ui/cypress/tests/pipeline/multiUser/pipelineMultiUserSupport.ts
new file mode 100644
index 0000000000..31f61716e4
--- /dev/null
+++ b/ui/cypress/tests/pipeline/multiUser/pipelineMultiUserSupport.ts
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ *
+ */
+
+import { UserUtils } from '../../../support/utils/UserUtils';
+import { UserRole } from '../../../../src/app/_enums/user-role.enum';
+import { PipelineUtils } from '../../../support/utils/pipeline/PipelineUtils';
+
+describe('Test Pipeline Multi User support', () => {
+ beforeEach('Setup Test', () => {
+ cy.initStreamPipesTest();
+ });
+
+ it('Test with pipeline admin account', () => {
+ testPipelineNotShownForOtherUsers(UserRole.ROLE_PIPELINE_ADMIN);
+ });
+
+ it('Test with pipeline user account', () => {
+ testPipelineNotShownForOtherUsers(UserRole.ROLE_PIPELINE_USER);
+ });
+
+ /**
+ * This function validates that the pipeline is only shown to
+ * the user who created it
+ */
+ function testPipelineNotShownForOtherUsers(userRole: UserRole) {
+ const pipelineAdminUser = UserUtils.createUser('user1', userRole);
+
+ PipelineUtils.addSampleAdapterAndPipeline();
+
+ UserUtils.switchUser(pipelineAdminUser);
+
+ PipelineUtils.checkAmountOfPipelinesPipeline(0);
+ }
+});
diff --git a/ui/cypress/tests/pipeline/pipelineTest.smoke.spec.ts
b/ui/cypress/tests/pipeline/pipelineTest.smoke.spec.ts
index 010f7695f9..f17be7abc5 100644
--- a/ui/cypress/tests/pipeline/pipelineTest.smoke.spec.ts
+++ b/ui/cypress/tests/pipeline/pipelineTest.smoke.spec.ts
@@ -16,36 +16,15 @@
*
*/
-import { ConnectUtils } from '../../support/utils/connect/ConnectUtils';
import { PipelineUtils } from '../../support/utils/pipeline/PipelineUtils';
-import { PipelineElementBuilder } from
'../../support/builder/PipelineElementBuilder';
-import { PipelineBuilder } from '../../support/builder/PipelineBuilder';
-
-const adapterName = 'simulator';
describe('Test Random Data Simulator Stream Adapter', () => {
beforeEach('Setup Test', () => {
cy.initStreamPipesTest();
- ConnectUtils.addMachineDataSimulator(adapterName);
});
it('Perform Test', () => {
- const pipelineInput = PipelineBuilder.create('Pipeline Test')
- .addSource(adapterName)
- .addProcessingElement(
- PipelineElementBuilder.create('field_renamer')
- .addInput('drop-down', 'convert-property', 'timestamp')
- .addInput('input', 'field-name', 't')
- .build(),
- )
- .addSink(
- PipelineElementBuilder.create('data_lake')
- .addInput('input', 'db_measurement', 'demo')
- .build(),
- )
- .build();
-
- PipelineUtils.addPipeline(pipelineInput);
+ PipelineUtils.addSampleAdapterAndPipeline();
PipelineUtils.deletePipeline();
});
});
diff --git a/ui/cypress/tests/pipeline/updatePipelineTest.smoke.spec.ts
b/ui/cypress/tests/pipeline/updatePipelineTest.smoke.spec.ts
index 5de2359906..f1ff02d0c9 100644
--- a/ui/cypress/tests/pipeline/updatePipelineTest.smoke.spec.ts
+++ b/ui/cypress/tests/pipeline/updatePipelineTest.smoke.spec.ts
@@ -16,36 +16,15 @@
*
*/
-import { ConnectUtils } from '../../support/utils/connect/ConnectUtils';
import { PipelineUtils } from '../../support/utils/pipeline/PipelineUtils';
-import { PipelineElementBuilder } from
'../../support/builder/PipelineElementBuilder';
-import { PipelineBuilder } from '../../support/builder/PipelineBuilder';
-
-const adapterName = 'simulator';
describe('Test update of running pipeline', () => {
beforeEach('Setup Test', () => {
cy.initStreamPipesTest();
- ConnectUtils.addMachineDataSimulator(adapterName);
});
it('Perform Test', () => {
- const pipelineInput = PipelineBuilder.create('Pipeline Test')
- .addSource(adapterName)
- .addProcessingElement(
- PipelineElementBuilder.create('field_renamer')
- .addInput('drop-down', 'convert-property', 'timestamp')
- .addInput('input', 'field-name', 't')
- .build(),
- )
- .addSink(
- PipelineElementBuilder.create('data_lake')
- .addInput('input', 'db_measurement', 'demo')
- .build(),
- )
- .build();
-
- PipelineUtils.addPipeline(pipelineInput);
+ PipelineUtils.addSampleAdapterAndPipeline();
PipelineUtils.editPipeline();
cy.wait(1000);
PipelineUtils.startPipeline();
diff --git a/ui/cypress/tests/userManagement/testGroupManagement.spec.ts
b/ui/cypress/tests/userManagement/testGroupManagement.spec.ts
index 03d7159568..4c62669b2b 100644
--- a/ui/cypress/tests/userManagement/testGroupManagement.spec.ts
+++ b/ui/cypress/tests/userManagement/testGroupManagement.spec.ts
@@ -23,6 +23,8 @@ import { ConnectUtils } from
'../../support/utils/connect/ConnectUtils';
import { PipelineUtils } from '../../support/utils/pipeline/PipelineUtils';
import { PipelineElementBuilder } from
'../../support/builder/PipelineElementBuilder';
import { PipelineBuilder } from '../../support/builder/PipelineBuilder';
+import { GeneralUtils } from '../../support/utils/GeneralUtils';
+import { PermissionUtils } from '../../support/utils/user/PermissionUtils';
describe('Test Group Management for Pipelines', () => {
beforeEach('Setup Test', () => {
@@ -94,18 +96,15 @@ describe('Test Group Management for Pipelines', () => {
// Add user group to pipeline
PipelineUtils.goToPipelines();
- cy.dataCy('share').click();
+ PermissionUtils.openManagePermissions();
cy.get('label').contains('Authorized Groups').click();
cy.get('mat-option').contains('User_Group').click();
- cy.dataCy('sp-element-edit-user-save').click();
+ PermissionUtils.save();
// Login as first user which belongs to user group with pipeline admin
role
UserUtils.switchUser(user);
- cy.dataCy('navigation-icon', { timeout: 10000 }).should(
- 'have.length',
- 4,
- );
+ GeneralUtils.validateAmountOfNavigationIcons(4);
// Check if pipeline is visible
PipelineUtils.goToPipelines();
@@ -121,10 +120,7 @@ describe('Test Group Management for Pipelines', () => {
// Login as user2
UserUtils.switchUser(user2);
- cy.dataCy('navigation-icon', { timeout: 10000 }).should(
- 'have.length',
- 3,
- );
+ GeneralUtils.validateAmountOfNavigationIcons(3);
// Check if pipeline is invisible to user2
PipelineUtils.goToPipelines();
diff --git a/ui/cypress/tests/userManagement/testUserRoleConnect.spec.ts
b/ui/cypress/tests/userManagement/testUserRoleConnect.spec.ts
index 5b0dace368..04290d02ca 100644
--- a/ui/cypress/tests/userManagement/testUserRoleConnect.spec.ts
+++ b/ui/cypress/tests/userManagement/testUserRoleConnect.spec.ts
@@ -15,48 +15,53 @@
* limitations under the License.
*
*/
-import { UserBuilder } from '../../support/builder/UserBuilder';
import { UserRole } from '../../../src/app/_enums/user-role.enum';
import { UserUtils } from '../../support/utils/UserUtils';
import { ConnectUtils } from '../../support/utils/connect/ConnectUtils';
-import { ConnectBtns } from '../../support/utils/connect/ConnectBtns';
+import { PermissionUtils } from '../../support/utils/user/PermissionUtils';
+import { GeneralUtils } from '../../support/utils/GeneralUtils';
describe('Test User Roles for Connect', () => {
+ var connectAdminUser;
beforeEach('Setup Test', () => {
cy.initStreamPipesTest();
+ connectAdminUser = UserUtils.createUser(
+ 'user',
+ UserRole.ROLE_CONNECT_ADMIN,
+ );
ConnectUtils.addMachineDataSimulator('simulator');
});
- it('Perform Test', () => {
- // Add connect admin user
- const connect_admin = UserBuilder.create('[email protected]')
- .setName('connect_admin')
- .setPassword('password')
- .addRole(UserRole.ROLE_CONNECT_ADMIN)
- .build();
- UserUtils.addUser(connect_admin);
-
- // Login as user and check if connect is visible to user
- UserUtils.switchUser(connect_admin);
-
- cy.dataCy('navigation-icon', { timeout: 10000 }).should(
- 'have.length',
- 3,
- );
+ it('Connect admin should not see adapters of other users', () => {
+ UserUtils.switchUser(connectAdminUser);
- ConnectUtils.goToConnect();
- cy.dataCy('all-adapters-table', { timeout: 10000 }).should(
- 'have.length',
- 1,
- );
- cy.dataCy('all-adapters-table', { timeout: 10000 }).should(
- 'contain',
- 'simulator',
- );
+ GeneralUtils.validateAmountOfNavigationIcons(3);
+
+ // Validate that no adapter is visible
+ ConnectUtils.checkAmountOfAdapters(0);
+ });
+
+ it('Connect admin should see public adapters of other users', () => {
+ // Set adapter to public
+ PermissionUtils.markElementAsPublic();
+
+ UserUtils.switchUser(connectAdminUser);
+
+ GeneralUtils.validateAmountOfNavigationIcons(3);
+
+ // Validate that adapter is visible
+ ConnectUtils.checkAmountOfAdapters(1);
+ });
+
+ it('Connect admin should see shared adapters of other users', () => {
+ // Share adapter with user
+ PermissionUtils.authorizeUser(connectAdminUser.email);
+
+ UserUtils.switchUser(connectAdminUser);
+
+ GeneralUtils.validateAmountOfNavigationIcons(3);
- // validate that adapter can be stopped and edited
- ConnectBtns.stopAdapter().click();
- ConnectBtns.editAdapter().should('not.be.disabled');
- ConnectBtns.editAdapter().click();
+ // Validate that adapter is visible
+ ConnectUtils.checkAmountOfAdapters(1);
});
});
diff --git a/ui/cypress/tests/userManagement/testUserRolePipeline.spec.ts
b/ui/cypress/tests/userManagement/testUserRolePipeline.spec.ts
index 94d83418ff..3c690ffe92 100644
--- a/ui/cypress/tests/userManagement/testUserRolePipeline.spec.ts
+++ b/ui/cypress/tests/userManagement/testUserRolePipeline.spec.ts
@@ -16,88 +16,90 @@
*
*/
-import { UserBuilder } from '../../support/builder/UserBuilder';
import { UserRole } from '../../../src/app/_enums/user-role.enum';
import { UserUtils } from '../../support/utils/UserUtils';
import { ConnectUtils } from '../../support/utils/connect/ConnectUtils';
import { PipelineUtils } from '../../support/utils/pipeline/PipelineUtils';
-import { PipelineElementBuilder } from
'../../support/builder/PipelineElementBuilder';
-import { PipelineBuilder } from '../../support/builder/PipelineBuilder';
+import { GeneralUtils } from '../../support/utils/GeneralUtils';
+import { PermissionUtils } from '../../support/utils/user/PermissionUtils';
+import { PipelineBtns } from '../../support/utils/pipeline/PipelineBtns';
describe('Test User Roles for Pipelines', () => {
beforeEach('Setup Test', () => {
cy.initStreamPipesTest();
- ConnectUtils.addMachineDataSimulator('simulator');
- const pipelineInput = PipelineBuilder.create('Pipeline Test')
- .addSource('simulator')
- .addProcessingElement(
- PipelineElementBuilder.create('field_renamer')
- .addInput('drop-down', 'convert-property', 'timestamp')
- .addInput('input', 'field-name', 't')
- .build(),
- )
- .addSink(
- PipelineElementBuilder.create('data_lake')
- .addInput('input', 'db_measurement', 'demo')
- .build(),
- )
- .build();
-
- PipelineUtils.addPipeline(pipelineInput);
+ // Create a machine data simulator with a sample pipeline for the tests
+ ConnectUtils.addMachineDataSimulator('simulator', true);
});
- it('Perform Test', () => {
- // Add new user
- UserUtils.goToUserConfiguration();
-
- cy.dataCy('user-accounts-table-row', { timeout: 10000 }).should(
- 'have.length',
- 1,
+ it('Pipeline admin should not see pipelines of other users', () => {
+ const newUser = UserUtils.createUser(
+ 'user',
+ UserRole.ROLE_PIPELINE_ADMIN,
);
- const email = '[email protected]';
- const name = 'test_user';
- const user = UserBuilder.create(email)
- .setName(name)
- .setPassword(name)
- .addRole(UserRole.ROLE_PIPELINE_USER)
- .build();
+ // Login as user and check if pipeline is visible to user
+ UserUtils.switchUser(newUser);
+
+ GeneralUtils.validateAmountOfNavigationIcons(4);
- UserUtils.addUser(user);
+ PipelineUtils.goToPipelines();
+ PipelineUtils.checkAmountOfPipelinesPipeline(0);
+ });
- // Check if user is added successfully
- cy.dataCy('user-accounts-table-row', { timeout: 10000 }).should(
- 'have.length',
- 2,
+ it('Pipeline admin should see public pipelines of other users', () => {
+ const newUser = UserUtils.createUser(
+ 'user',
+ UserRole.ROLE_PIPELINE_ADMIN,
);
// Add new authorized user to pipeline
PipelineUtils.goToPipelines();
- cy.dataCy('share').click();
- cy.get('label').contains('Authorized Users').click();
- cy.get('mat-option').contains(email).click();
- cy.dataCy('sp-element-edit-user-save').click();
+ PermissionUtils.markElementAsPublic();
// Login as user and check if pipeline is visible to user
- UserUtils.switchUser(user);
+ UserUtils.switchUser(newUser);
+
+ PipelineUtils.goToPipelines();
+ PipelineUtils.checkAmountOfPipelinesPipeline(1);
+ });
- cy.dataCy('navigation-icon', { timeout: 10000 }).should(
- 'have.length',
- 3,
+ it(' Pipeline admin should see shared pipelines of other users', () => {
+ const newUser = UserUtils.createUser(
+ 'user',
+ UserRole.ROLE_PIPELINE_ADMIN,
);
+ // Add new authorized user to pipeline
PipelineUtils.goToPipelines();
- cy.dataCy('all-pipelines-table', { timeout: 10000 }).should(
- 'have.length',
- 1,
- );
- cy.dataCy('all-pipelines-table', { timeout: 10000 }).should(
- 'contain',
- 'Pipeline Test',
+ PermissionUtils.markElementAsPublic();
+ PermissionUtils.authorizeUser(newUser.email);
+
+ // Login as user and check if pipeline is visible to user
+ UserUtils.switchUser(newUser);
+
+ PipelineUtils.goToPipelines();
+ PipelineUtils.checkAmountOfPipelinesPipeline(1);
+ });
+
+ it(' Pipeline user should see shared pipelines of other users but not be
able to edit them', () => {
+ const newUser = UserUtils.createUser(
+ 'user',
+ UserRole.ROLE_PIPELINE_USER,
);
- // Delete user
- UserUtils.switchUser(UserUtils.adminUser);
- UserUtils.deleteUser(user);
+ // Add new authorized user to pipeline
+ PipelineUtils.goToPipelines();
+ // PermissionUtils.markElementAsPublic();
+ PermissionUtils.authorizeUser(newUser.email);
+
+ // Login as user and check if pipeline is visible to user
+ UserUtils.switchUser(newUser);
+
+ PipelineUtils.goToPipelines();
+ PipelineUtils.checkAmountOfPipelinesPipeline(1);
+
+ // A pipeline user should not be able to stop the pipeline or delete it
+ PipelineBtns.deletePipeline().should('not.exist');
+ PipelineBtns.stopPipeline().should('be.disabled');
});
});
diff --git a/ui/cypress/tests/userManagement/testVariousUserRoles.smoke.spec.ts
b/ui/cypress/tests/userManagement/testVariousUserRoles.smoke.spec.ts
index 34e4624d63..4c00e01667 100644
--- a/ui/cypress/tests/userManagement/testVariousUserRoles.smoke.spec.ts
+++ b/ui/cypress/tests/userManagement/testVariousUserRoles.smoke.spec.ts
@@ -19,6 +19,7 @@
import { UserBuilder } from '../../support/builder/UserBuilder';
import { UserRole } from '../../../src/app/_enums/user-role.enum';
import { UserUtils } from '../../support/utils/UserUtils';
+import { GeneralUtils } from '../../support/utils/GeneralUtils';
const testedRoles = [
UserRole.ROLE_PIPELINE_ADMIN,
@@ -38,10 +39,7 @@ for (var i = 0; i < testedRoles.length; i++) {
it('Perform Test', () => {
// Add new user
UserUtils.goToUserConfiguration();
- cy.dataCy('navigation-icon', { timeout: 10000 }).should(
- 'have.length',
- 8,
- );
+ GeneralUtils.validateAmountOfNavigationIcons(8);
cy.dataCy('user-accounts-table-row', { timeout: 10000 }).should(
'have.length',
@@ -68,30 +66,15 @@ for (var i = 0; i < testedRoles.length; i++) {
// Check if every role displays correct navigation menu
if (testRole == UserRole.ROLE_PIPELINE_ADMIN) {
- cy.dataCy('navigation-icon', { timeout: 10000 }).should(
- 'have.length',
- 4,
- );
+ GeneralUtils.validateAmountOfNavigationIcons(4);
} else if (testRole == UserRole.ROLE_DASHBOARD_ADMIN) {
- cy.dataCy('navigation-icon', { timeout: 10000 }).should(
- 'have.length',
- 4,
- );
+ GeneralUtils.validateAmountOfNavigationIcons(4);
} else if (testRole == UserRole.ROLE_DATA_EXPLORER_ADMIN) {
- cy.dataCy('navigation-icon', { timeout: 10000 }).should(
- 'have.length',
- 4,
- );
+ GeneralUtils.validateAmountOfNavigationIcons(4);
} else if (testRole == UserRole.ROLE_CONNECT_ADMIN) {
- cy.dataCy('navigation-icon', { timeout: 10000 }).should(
- 'have.length',
- 3,
- );
+ GeneralUtils.validateAmountOfNavigationIcons(3);
} else if (testRole == UserRole.ROLE_ASSET_ADMIN) {
- cy.dataCy('navigation-icon', { timeout: 10000 }).should(
- 'have.length',
- 3,
- );
+ GeneralUtils.validateAmountOfNavigationIcons(3);
}
// Login as admin and delete user
diff --git
a/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.html
b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.html
index 5e8302acb3..52024357ff 100644
---
a/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.html
+++
b/ui/projects/streampipes/shared-ui/src/lib/components/sp-table/sp-table.component.html
@@ -24,7 +24,11 @@
<tr mat-row *matRowDef="let row; columns: columns"></tr>
<tr class="mat-row" *matNoDataRow>
- <td class="mat-cell" [colSpan]="columns.length">
+ <td
+ data-cy="no-table-entries"
+ class="mat-cell"
+ [colSpan]="columns.length"
+ >
No entries available.
</td>
</tr>
diff --git
a/ui/src/app/connect/components/existing-adapters/existing-adapters.component.html
b/ui/src/app/connect/components/existing-adapters/existing-adapters.component.html
index 5c1b11994c..6147d2cf9c 100644
---
a/ui/src/app/connect/components/existing-adapters/existing-adapters.component.html
+++
b/ui/src/app/connect/components/existing-adapters/existing-adapters.component.html
@@ -258,6 +258,7 @@
mat-icon-button
matTooltip="Manage permissions"
matTooltipPosition="above"
+ data-cy="open-manage-permissions"
*ngIf="isAdmin"
(click)="showPermissionsDialog(adapter)"
>
diff --git
a/ui/src/app/core-ui/object-permission-dialog/object-permission-dialog.component.html
b/ui/src/app/core-ui/object-permission-dialog/object-permission-dialog.component.html
index cf16da3273..ba806e1b7b 100644
---
a/ui/src/app/core-ui/object-permission-dialog/object-permission-dialog.component.html
+++
b/ui/src/app/core-ui/object-permission-dialog/object-permission-dialog.component.html
@@ -33,7 +33,10 @@
>
</mat-select>
</mat-form-field>
- <mat-checkbox formControlName="publicElement">
+ <mat-checkbox
+ data-cy="permission-public-element"
+ formControlName="publicElement"
+ >
Public Element
</mat-checkbox>
</div>
@@ -66,6 +69,7 @@
[matChipInputSeparatorKeyCodes]="
separatorKeysCodes
"
+ data-cy="authorized-user"
(matChipInputTokenEnd)="addUser($event)"
/>
</mat-chip-grid>
@@ -76,6 +80,7 @@
<mat-option
*ngFor="let user of filteredUsers | async"
[value]="user"
+ [attr.data-cy]="'user-option-' + user.username"
>
{{ user.username }}
</mat-option>
@@ -140,7 +145,7 @@
(click)="save()"
style="margin-right: 10px"
[disabled]="!parentForm.valid"
- data-cy="sp-element-edit-user-save"
+ data-cy="sp-manage-permissions-save"
>
<i class="material-icons">save</i><span> Save</span>
</button>
diff --git
a/ui/src/app/core-ui/object-permission-dialog/object-permission-dialog.component.ts
b/ui/src/app/core-ui/object-permission-dialog/object-permission-dialog.component.ts
index 1f21a19d03..dc9e66c336 100644
---
a/ui/src/app/core-ui/object-permission-dialog/object-permission-dialog.component.ts
+++
b/ui/src/app/core-ui/object-permission-dialog/object-permission-dialog.component.ts
@@ -231,7 +231,7 @@ export class ObjectPermissionDialogComponent implements
OnInit {
private addUserToSelection(authority: PermissionEntry) {
const user = this.allUsers.find(u => u.principalId === authority.sid);
- this.grantedUserAuthorities.push(user);
+ user && this.grantedUserAuthorities.push(user);
}
private addGroupToSelection(authority: PermissionEntry) {
diff --git
a/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.html
b/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.html
index f9918daaa0..1c1bf59463 100644
---
a/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.html
+++
b/ui/src/app/pipelines/components/pipeline-overview/pipeline-overview.component.html
@@ -28,6 +28,7 @@
<div>
<div fxLayout="row" fxLayoutAlign="start center"
fxFlex="150px">
<div
+ data-cy="status-pipeline-green"
*ngIf="
pipeline.running && pipeline.healthStatus === 'OK'
"
@@ -116,6 +117,7 @@
mat-icon-button
matTooltip="Stop pipeline"
matTooltipPosition="above"
+ data-cy="stop-pipeline-button"
(click)="
pipelineOperationsService.stopPipeline(
pipeline._id,
@@ -192,7 +194,7 @@
refreshPipelinesEmitter
)
"
- data-cy="share"
+ data-cy="open-manage-permissions"
>
<i class="material-icons">share</i>
</button>