Add Target(Type)Management tests (#2735)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-10-10 08:36:08 +03:00
committed by GitHub
parent 0519c5229e
commit e7d9ee7990
16 changed files with 586 additions and 484 deletions

View File

@@ -19,6 +19,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch;
import org.eclipse.hawkbit.repository.test.util.WithUser;
@@ -26,7 +27,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.springframework.test.context.TestPropertySource;
@TestPropertySource(properties = "hawkbit.acm.access-controller.enabled=true")
abstract class AbstractAccessControllerTest extends AbstractJpaIntegrationTest {
abstract class AbstractAccessControllerManagementTest extends AbstractJpaIntegrationTest {
protected SoftwareModuleType smType1;
protected SoftwareModuleType smType2;
@@ -44,10 +45,13 @@ abstract class AbstractAccessControllerTest extends AbstractJpaIntegrationTest {
protected TargetType targetType1;
protected TargetType targetType2;
protected TargetTag targetTag1;
protected TargetTag targetTag2;
protected Target target1Type1;
protected Target target2Type2;
protected Target target3Type2;
@SuppressWarnings("java:S1117") // java:S1117 - intentional hiding of fiends - they are the same
@BeforeEach
@Override
public void beforeAll() throws Exception {
@@ -61,9 +65,9 @@ abstract class AbstractAccessControllerTest extends AbstractJpaIntegrationTest {
dsType1 = testdataFactory.findOrCreateDistributionSetType("DsType1", "DistributionSetType-1", List.of(smType1), List.of());
dsType2 = testdataFactory.findOrCreateDistributionSetType("DsType2", "DistributionSetType-2", List.of(smType2), List.of(smType1));
final List<DistributionSetTag> tags = testdataFactory.createDistributionSetTags(2);
dsTag1 = tags.get(0);
dsTag2 = tags.get(1);
final List<DistributionSetTag> dsTags = testdataFactory.createDistributionSetTags(2);
dsTag1 = dsTags.get(0);
dsTag2 = dsTags.get(1);
ds1Type1 = distributionSetManagement.lock(
distributionSetManagement.assignTag(
List.of(testdataFactory.createDistributionSet("Ds1Type1", "1.0", dsType1, List.of(sm1Type1)).getId()),
@@ -79,9 +83,18 @@ abstract class AbstractAccessControllerTest extends AbstractJpaIntegrationTest {
targetType1 = testdataFactory.createTargetType("TargetType1", Set.of(dsType1, dsType2));
targetType2 = testdataFactory.createTargetType("TargetType2", Set.of(dsType2));
target1Type1 = testdataFactory.createTarget("controller_1", "Controller-1", targetType1);
target2Type2 = testdataFactory.createTarget("controller_2", "Controller-2", targetType2);
target3Type2 = testdataFactory.createTarget("controller_3", "Controller-3", targetType2);
final List<? extends TargetTag> targetTags = testdataFactory.createTargetTags(2, "acm_");
targetTag1 = targetTags.get(0);
targetTag2 = targetTags.get(1);
final Target target1Type1 = testdataFactory.createTarget("controller_1", "Controller-1", targetType1);
final Target target2Type2 = testdataFactory.createTarget("controller_2", "Controller-2", targetType2);
final Target target3Type2 = testdataFactory.createTarget("controller_3", "Controller-3", targetType2);
targetManagement.assignTag(List.of(target1Type1.getControllerId(), target2Type2.getControllerId()), targetTag1.getId());
targetManagement.assignTag(
List.of(target1Type1.getControllerId(), target2Type2.getControllerId(), target3Type2.getControllerId()), targetTag2.getId());
this.target1Type1 = targetManagement.get(target1Type1.getId());
this.target2Type2 = targetManagement.get(target2Type2.getId());
this.target3Type2 = targetManagement.get(target3Type2.getId());
}
protected static WithUser withAuthorities(final String... authorities) {

View File

@@ -30,7 +30,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.integration.support.locks.LockRegistry;
class AutoAssignTest extends AbstractAccessControllerTest {
class AutoAssignTest extends AbstractAccessControllerManagementTest {
@Autowired
AutoAssignExecutor autoAssignExecutor;

View File

@@ -27,7 +27,7 @@ import org.eclipse.hawkbit.repository.model.DeploymentRequest;
import org.eclipse.hawkbit.repository.model.Target;
import org.junit.jupiter.api.Test;
class DeploymentManagementTest extends AbstractAccessControllerTest {
class DeploymentManagementTest extends AbstractAccessControllerManagementTest {
@Test
void verifyAssignments() {

View File

@@ -32,7 +32,7 @@ import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch;
import org.junit.jupiter.api.Test;
class DistributionSetManagementTest extends AbstractAccessControllerTest {
class DistributionSetManagementTest extends AbstractAccessControllerManagementTest {
@Test
void verifyCreate() {

View File

@@ -30,11 +30,11 @@ import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.junit.jupiter.api.Test;
class DistributionSetTypeManagementTest extends AbstractAccessControllerTest {
class DistributionSetTypeManagementTest extends AbstractAccessControllerManagementTest {
@Test
void verifyCreate() {
// permissions to read only type1 ds types and create new type with name 'permitted'
// permissions to read all and create new type with name 'permitted'
runAs(withAuthorities(READ_PREFIX + DISTRIBUTION_SET_TYPE, CREATE_PREFIX + DISTRIBUTION_SET_TYPE + "/name==permitted"), () -> {
assertThat(testdataFactory.findOrCreateDistributionSetType("newType", "permitted")).isNotNull();
assertThatThrownBy(() -> testdataFactory.findOrCreateDistributionSetType("newType_2", "not_permitted_2"))

View File

@@ -18,14 +18,16 @@ import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_TARGET;
import static org.eclipse.hawkbit.im.authentication.SpPermission.UPDATE_TARGET;
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.runAs;
import java.util.Arrays;
import java.util.Optional;
import org.eclipse.hawkbit.repository.Identifiable;
import org.eclipse.hawkbit.repository.jpa.rollout.RolloutScheduler;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Target;
import org.junit.jupiter.api.Test;
class RolloutExecutionTest extends AbstractAccessControllerTest {
class RolloutExecutionTest extends AbstractAccessControllerManagementTest {
@Test
void verifyOnlyUpdatableTargetsArePartOfRolloutExecutedByScheduler() {
@@ -66,7 +68,8 @@ class RolloutExecutionTest extends AbstractAccessControllerTest {
assertThat(rolloutGroupManagement.findByRollout(rollout.getId(), UNPAGED).getContent().stream()
.flatMap(rolloutGroup -> rolloutGroupManagement.findTargetsOfRolloutGroup(rolloutGroup.getId(), UNPAGED).stream()))
.extracting(Identifiable::getId)
.as("Only updatable targets should be part of the rollout")
.containsExactly(expectedTargets);
.containsExactly(Arrays.stream(expectedTargets).map(Identifiable::getId).toArray(Long[]::new));
}
}

View File

@@ -21,7 +21,7 @@ import org.eclipse.hawkbit.repository.Identifiable;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.junit.jupiter.api.Test;
class RolloutManagementTest extends AbstractAccessControllerTest {
class RolloutManagementTest extends AbstractAccessControllerManagementTest {
/**
* Test is verifying that rollout details are showing details without restrictions

View File

@@ -28,7 +28,7 @@ import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModule.MetadataValueCreate;
import org.junit.jupiter.api.Test;
class SoftwareModuleManagementTest extends AbstractAccessControllerTest {
class SoftwareModuleManagementTest extends AbstractAccessControllerManagementTest {
@Test
void verifyRead() {

View File

@@ -29,11 +29,11 @@ import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.junit.jupiter.api.Test;
class SoftwareModuleTypeManagementTest extends AbstractAccessControllerTest {
class SoftwareModuleTypeManagementTest extends AbstractAccessControllerManagementTest {
@Test
void verifyCreate() {
// permissions to read only type1 sm types and create new type with name 'permitted'
// permissions to read all and create new type with name 'permitted'
runAs(withAuthorities(READ_PREFIX + SOFTWARE_MODULE_TYPE, CREATE_PREFIX + SOFTWARE_MODULE_TYPE + "/key==permitted"), () -> {
assertThat(testdataFactory.findOrCreateSoftwareModuleType("permitted")).isNotNull();
assertThatThrownBy(() -> testdataFactory.findOrCreateSoftwareModuleType("not_permitted_2"))

View File

@@ -0,0 +1,116 @@
/**
* Copyright (c) 2025 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.acm;
import static org.assertj.core.api.Assertions.assertThat;
import static org.eclipse.hawkbit.im.authentication.SpPermission.CREATE_PREFIX;
import static org.eclipse.hawkbit.im.authentication.SpPermission.CREATE_TARGET;
import static org.eclipse.hawkbit.im.authentication.SpPermission.DELETE_PREFIX;
import static org.eclipse.hawkbit.im.authentication.SpPermission.DELETE_TARGET;
import static org.eclipse.hawkbit.im.authentication.SpPermission.DISTRIBUTION_SET;
import static org.eclipse.hawkbit.im.authentication.SpPermission.DISTRIBUTION_SET_TYPE;
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_DISTRIBUTION_SET;
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_PREFIX;
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_TARGET;
import static org.eclipse.hawkbit.im.authentication.SpPermission.SOFTWARE_MODULE;
import static org.eclipse.hawkbit.im.authentication.SpPermission.SOFTWARE_MODULE_TYPE;
import static org.eclipse.hawkbit.im.authentication.SpPermission.TARGET_TYPE;
import static org.eclipse.hawkbit.im.authentication.SpPermission.UPDATE_PREFIX;
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.runAs;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import java.util.List;
import org.eclipse.hawkbit.repository.jpa.acm.AccessController.Operation;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specification;
class SystemExecutionTest extends AbstractAccessControllerManagementTest {
@Autowired
protected AccessController<JpaSoftwareModule> softwareModuleAccessController;
@Autowired
protected AccessController<JpaSoftwareModuleType> softwareModuleTypeAccessController;
@Autowired
protected AccessController<JpaDistributionSet> distributionSetAccessController;
@Autowired
protected AccessController<JpaDistributionSetType> distributionSetTypeAccessController;
@Autowired
protected AccessController<JpaTarget> targetAccessController;
@Autowired
protected AccessController<JpaTargetType> targetTypeAccessController;
@Autowired
protected AccessController<JpaAction> actionAccessController;
@Autowired
List<AccessController<?>> allAccessControllers;
@SuppressWarnings({ "rawtypes", "unchecked" })
@Test
void verifyWithoutScopes() {
allAccessControllers.forEach(accessController -> {
for (final Operation operation : Operation.values()) {
assertThat(accessController.getAccessRules(operation)).isEmpty();
}
final Specification specificationMock = mock(Specification.class);
for (final Operation operation : Operation.values()) {
accessController.appendAccessRules(operation, specificationMock);
verifyNoInteractions(specificationMock);
}
});
}
@SuppressWarnings("unchecked")
@Test
void verifyWithScope() {
runAs(withAuthorities(READ_PREFIX + SOFTWARE_MODULE + "/type.id==1", UPDATE_PREFIX + SOFTWARE_MODULE),
() -> verifyAccessController(softwareModuleAccessController));
runAs(withAuthorities(READ_PREFIX + SOFTWARE_MODULE_TYPE + "/id==1", UPDATE_PREFIX + SOFTWARE_MODULE_TYPE),
() -> verifyAccessController(softwareModuleTypeAccessController));
runAs(withAuthorities(READ_DISTRIBUTION_SET + "/type.id==1", CREATE_PREFIX + DISTRIBUTION_SET),
() -> verifyAccessController(distributionSetAccessController));
runAs(withAuthorities(READ_PREFIX + DISTRIBUTION_SET_TYPE + "/id==1", DELETE_PREFIX + DISTRIBUTION_SET_TYPE),
() -> verifyAccessController(distributionSetTypeAccessController));
runAs(withAuthorities(READ_TARGET, DELETE_TARGET + "/type.id==1"),
() -> verifyAccessController(targetAccessController));
runAs(withAuthorities(UPDATE_PREFIX + TARGET_TYPE + "/id==1"), () -> verifyAccessController(targetTypeAccessController));
runAs(withAuthorities(CREATE_TARGET + "/type.id==1"), () -> verifyAccessController(actionAccessController));
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void verifyAccessController(final AccessController<?> accessController) {
final Specification mock = mock(Specification.class);
for (final Operation operation : Operation.values()) {
accessController.appendAccessRules(operation, mock);
}
verify(mock, times(1)).and(any()); // once for every access controller is scoped only
final Specification mockAsSystem = mock(Specification.class);
for (Operation operation : Operation.values()) {
systemSecurityContext.runAsSystem(() -> {
accessController.appendAccessRules(operation, mockAsSystem);
return null;
});
}
verifyNoInteractions(mockAsSystem);
}
}

View File

@@ -1,319 +0,0 @@
/**
* Copyright (c) 2025 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.acm;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.eclipse.hawkbit.im.authentication.SpPermission.CREATE_ROLLOUT;
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_DISTRIBUTION_SET;
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_ROLLOUT;
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_TARGET;
import static org.eclipse.hawkbit.im.authentication.SpPermission.UPDATE_TARGET;
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.runAs;
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.withUser;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.eclipse.hawkbit.repository.Identifiable;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement.AutoAssignDistributionSetUpdate;
import org.eclipse.hawkbit.repository.TargetManagement.Create;
import org.eclipse.hawkbit.repository.TargetTagManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.jpa.autoassign.AutoAssignChecker;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.test.context.TestPropertySource;
/**
* Feature: Component Tests - Access Control<br/>
* Story: Test Target Access Controller
*/
@TestPropertySource(properties = "hawkbit.acm.access-controller.enabled=true")
class TargetAccessControllerTest extends AbstractJpaIntegrationTest {
@Autowired
AutoAssignChecker autoAssignChecker;
/**
* Verifies read access rules for targets
*/
@Test
void verifyTargetReadOperations() {
final Target permittedTarget = targetManagement
.create(Create.builder().controllerId("device01").updateStatus(TargetUpdateStatus.REGISTERED).build());
final Target hiddenTarget = targetManagement
.create(Create.builder().controllerId("device02").updateStatus(TargetUpdateStatus.REGISTERED).build());
runAs(withUser("user", READ_TARGET + "/controllerId==" + permittedTarget.getControllerId()), () -> {
// verify targetManagement#findAll
assertThat(targetManagement.findAll(Pageable.unpaged()).get().map(Identifiable::getId).toList())
.containsExactly(permittedTarget.getId());
// verify targetManagement#findByRsql
assertThat(targetManagement.findByRsql("id==*", Pageable.unpaged()).get().map(Identifiable::getId).toList())
.containsExactly(permittedTarget.getId());
// verify targetManagement#getByControllerID
assertThat(targetManagement.getByControllerId(permittedTarget.getControllerId())).isNotNull();
final String hiddenTargetControllerId = hiddenTarget.getControllerId();
assertThatThrownBy(() -> targetManagement.getByControllerId(hiddenTargetControllerId))
.as("Missing read permissions for hidden target.")
.isInstanceOf(InsufficientPermissionException.class);
assertThatThrownBy(() -> targetManagement.findByControllerId(hiddenTargetControllerId))
.as("Missing read permissions for hidden target.")
.isInstanceOf(InsufficientPermissionException.class);
// verify targetManagement#getByControllerId
assertThat(targetManagement
.findByControllerId(List.of(permittedTarget.getControllerId(), hiddenTargetControllerId))
.stream().map(Identifiable::getId).toList()).containsExactly(permittedTarget.getId());
// verify targetManagement#get
assertThat(targetManagement.find(permittedTarget.getId())).isPresent();
assertThat(targetManagement.find(hiddenTarget.getId())).isEmpty();
// verify targetManagement#get
final List<Long> withHidden = List.of(permittedTarget.getId(), hiddenTarget.getId());
assertThatExceptionOfType(EntityNotFoundException.class).isThrownBy(() -> targetManagement.get(withHidden));
// verify targetManagement#getControllerAttributes
assertThat(targetManagement.getControllerAttributes(permittedTarget.getControllerId())).isEmpty();
assertThatThrownBy(() -> targetManagement.getControllerAttributes(hiddenTargetControllerId))
.as("Target should not be found.")
.isInstanceOf(InsufficientPermissionException.class);
});
}
@Test
void verifyTagFilteringAndManagement() {
final Target permittedTarget = targetManagement
.create(Create.builder().controllerId("device01").updateStatus(TargetUpdateStatus.REGISTERED).build());
final Target readOnlyTarget = targetManagement
.create(Create.builder().controllerId("device02").updateStatus(TargetUpdateStatus.REGISTERED).build());
final String readOnlyTargetControllerId = readOnlyTarget.getControllerId();
final Target hiddenTarget = targetManagement
.create(Create.builder().controllerId("device03").updateStatus(TargetUpdateStatus.REGISTERED).build());
final Long myTagId = targetTagManagement.create(TargetTagManagement.Create.builder().name("myTag").build()).getId();
// perform tag assignment before setting access rules
targetManagement.assignTag(
List.of(permittedTarget.getControllerId(), readOnlyTargetControllerId, hiddenTarget.getControllerId()), myTagId);
runAs(withUser("user",
READ_TARGET + "/controllerId==" + permittedTarget.getControllerId() + " or controllerId==" + readOnlyTargetControllerId,
UPDATE_TARGET + "/controllerId==" + permittedTarget.getControllerId()),
() -> {
// verify targetManagement#findByTag
assertThat(
targetManagement.findByTag(myTagId, Pageable.unpaged()).get().map(Identifiable::getId).toList())
.containsExactlyInAnyOrder(permittedTarget.getId(), readOnlyTarget.getId());
// verify targetManagement#findByRsqlAndTag
assertThat(targetManagement.findByRsqlAndTag("id==*", myTagId, Pageable.unpaged()).get()
.map(Identifiable::getId).toList()).containsExactlyInAnyOrder(permittedTarget.getId(), readOnlyTarget.getId());
// verify targetManagement#assignTag on permitted target
assertThat(targetManagement.assignTag(Collections.singletonList(permittedTarget.getControllerId()), myTagId)).hasSize(1);
// verify targetManagement#unassignTag on permitted target
assertThat(targetManagement.unassignTag(Collections.singletonList(permittedTarget.getControllerId()), myTagId)).hasSize(1);
// verify targetManagement#assignTag on permitted target
assertThat(targetManagement.assignTag(Collections.singletonList(permittedTarget.getControllerId()), myTagId))
.hasSize(1);
// verify targetManagement#unAssignTag on permitted target
assertThat(targetManagement.unassignTag(List.of(permittedTarget.getControllerId()), myTagId).get(0).getControllerId())
.isEqualTo(permittedTarget.getControllerId());
// assignment is denied for readOnlyTarget (read, but no update permissions)
final List<String> readTargetControllerIdList = Collections.singletonList(readOnlyTargetControllerId);
assertThatThrownBy(() -> targetManagement.assignTag(readTargetControllerIdList, myTagId))
.as("Missing update permissions for target to toggle tag assignment.")
.isInstanceOfAny(InsufficientPermissionException.class);
// assignment is denied for readOnlyTarget (read, but no update permissions)
final List<String> readOnlyTargetControllerIdList = List.of(readOnlyTargetControllerId);
assertThatThrownBy(() -> targetManagement.unassignTag(readOnlyTargetControllerIdList, myTagId))
.as("Missing update permissions for target to toggle tag assignment.")
.isInstanceOf(InsufficientPermissionException.class);
// assignment is denied for hiddenTarget since it's hidden
final List<String> hiddenTargetControllerIdList = Collections.singletonList(hiddenTarget.getControllerId());
assertThatThrownBy(() -> targetManagement.assignTag(hiddenTargetControllerIdList, myTagId))
.as("Missing update permissions for target to toggle tag assignment.")
.isInstanceOf(InsufficientPermissionException.class);
// assignment is denied for hiddenTarget since it's hidden
assertThatThrownBy(() -> targetManagement.assignTag(hiddenTargetControllerIdList, myTagId))
.as("Missing update permissions for target to toggle tag assignment.")
.isInstanceOf(InsufficientPermissionException.class);
// assignment is denied for hiddenTarget since it's hidden
assertThatThrownBy(() -> targetManagement.unassignTag(hiddenTargetControllerIdList, myTagId))
.as("Missing update permissions for target to toggle tag assignment.")
.isInstanceOf(InsufficientPermissionException.class);
});
}
/**
* Verifies rules for target assignment
*/
@Test
void verifyTargetAssignment() {
final DistributionSet ds = testdataFactory.createDistributionSet("myDs");
distributionSetManagement.lock(ds);
final Target permittedTarget = targetManagement
.create(Create.builder().controllerId("device01").updateStatus(TargetUpdateStatus.REGISTERED).build());
final String hiddenTargetControllerId = targetManagement
.create(Create.builder().controllerId("device02").updateStatus(TargetUpdateStatus.REGISTERED).build())
.getControllerId();
runAs(withUser("user",
READ_DISTRIBUTION_SET,
READ_TARGET + "/controllerId==" + permittedTarget.getControllerId(),
UPDATE_TARGET + "/controllerId==" + permittedTarget.getControllerId()), () -> {
final Long dsId = ds.getId();
assertThat(assignDistributionSet(dsId, permittedTarget.getControllerId()).getAssigned()).isEqualTo(1);
// assigning of not allowed target behaves as not found
assertThatThrownBy(() -> assignDistributionSet(dsId, hiddenTargetControllerId)).isInstanceOf(AssertionError.class);
});
}
/**
* Verifies rules for target assignment
*/
@Test
void verifyTargetAssignmentOnNonUpdatableTarget() {
final DistributionSet firstDs = testdataFactory.createDistributionSet("myDs");
distributionSetManagement.lock(firstDs);
final DistributionSet secondDs = testdataFactory.createDistributionSet("anotherDs");
distributionSetManagement.lock(secondDs);
final Target manageableTarget = targetManagement
.create(Create.builder().controllerId("device01").updateStatus(TargetUpdateStatus.REGISTERED).build());
final Target readOnlyTarget = targetManagement
.create(Create.builder().controllerId("device02").updateStatus(TargetUpdateStatus.REGISTERED).build());
runAs(withUser("user",
READ_DISTRIBUTION_SET,
READ_TARGET + "/controllerId==" + manageableTarget.getControllerId() + " or controllerId==" + readOnlyTarget.getControllerId(),
UPDATE_TARGET + "/controllerId==" + manageableTarget.getControllerId()), () -> {
final Long firstDsId = firstDs.getId();
// assignment is permitted for manageableTarget
assertThat(assignDistributionSet(firstDsId, manageableTarget.getControllerId()).getAssigned()).isEqualTo(1);
// assignment is denied for readOnlyTarget (read, but no update permissions)
final var readOnlyTargetControllerId = readOnlyTarget.getControllerId();
assertThatThrownBy(() -> assignDistributionSet(firstDsId, readOnlyTargetControllerId)).isInstanceOf(AssertionError.class);
// bunch assignment skips denied since at least one target without update permissions is present
assertThat(assignDistributionSet(secondDs.getId(),
Arrays.asList(readOnlyTargetControllerId, manageableTarget.getControllerId()),
ActionType.FORCED).getAssigned()).isEqualTo(1);
});
}
/**
* Verifies only manageable targets are part of the rollout
*/
@Test
void verifyRolloutTargetScope() {
final DistributionSet ds = testdataFactory.createDistributionSet("myDs");
distributionSetManagement.lock(ds);
final String[] updateTargetControllerIds = { "update1", "update2", "update3" };
final List<Target> updateTargets = testdataFactory.createTargets(updateTargetControllerIds);
final String[] readTargetControllerIds = { "read1", "read2", "read3", "read4" };
final List<Target> readTargets = testdataFactory.createTargets(readTargetControllerIds);
final List<Target> hiddenTargets = testdataFactory.createTargets("hidden1", "hidden2", "hidden3", "hidden4", "hidden5");
runAs(withUser("user",
READ_DISTRIBUTION_SET,
READ_TARGET + "/controllerId=in=(" + String.join(", ", List.of(updateTargetControllerIds)) + ")" +
" or controllerId=in=(" + String.join(", ", List.of(readTargetControllerIds)) + ")",
UPDATE_TARGET + "/controllerId=in=(" + String.join(", ", List.of(updateTargetControllerIds)) + ")",
CREATE_ROLLOUT, READ_ROLLOUT), () -> {
final Rollout rollout = testdataFactory.createRolloutByVariables(
"testRollout", "description", updateTargets.size(), "id==*", ds, "50", "5");
assertThat(rollout.getTotalTargets()).isEqualTo(updateTargets.size());
final List<RolloutGroup> content = rolloutGroupManagement.findByRollout(rollout.getId(), Pageable.unpaged()).getContent();
assertThat(content).hasSize(updateTargets.size());
final List<Target> rolloutTargets = content.stream().flatMap(
group -> rolloutGroupManagement.findTargetsOfRolloutGroup(group.getId(), Pageable.unpaged()).get())
.toList();
assertThat(rolloutTargets).hasSize(updateTargets.size()).allMatch(
target -> updateTargets.stream().anyMatch(readTarget -> readTarget.getId().equals(target.getId())))
.noneMatch(target -> readTargets.stream()
.anyMatch(readTarget -> readTarget.getId().equals(target.getId())))
.noneMatch(target -> hiddenTargets.stream()
.anyMatch(readTarget -> readTarget.getId().equals(target.getId())));
});
}
/**
* Verifies only manageable targets are part of an auto assignment.
*/
@Test
void verifyAutoAssignmentTargetScope() {
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
distributionSetManagement.lock(distributionSet);
final String[] updateTargetControllerIds = { "update1", "update2", "update3" };
final List<Target> updateTargets = testdataFactory.createTargets(updateTargetControllerIds);
final String[] readTargetControllerIds = { "read1", "read2", "read3", "read4" };
final List<Target> readTargets = testdataFactory.createTargets(readTargetControllerIds);
final List<Target> hiddenTargets = testdataFactory.createTargets("hidden1", "hidden2", "hidden3", "hidden4", "hidden5");
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement
.create(TargetFilterQueryManagement.Create.builder().name("testName").query("id==*").build());
runAs(withUser("user",
READ_TARGET + "/controllerId=in=(" + String.join(", ", List.of(updateTargetControllerIds)) + ")" +
" or controllerId=in=(" + String.join(", ", List.of(readTargetControllerIds)) + ")",
UPDATE_TARGET + "/controllerId=in=(" + String.join(", ", List.of(updateTargetControllerIds)) + ")",
READ_DISTRIBUTION_SET + "/id==" + distributionSet.getId()), () -> {
targetFilterQueryManagement.updateAutoAssignDS(
new AutoAssignDistributionSetUpdate(targetFilterQuery.getId()).ds(distributionSet.getId()));
autoAssignChecker.checkAllTargets();
assertThat(targetManagement.findByAssignedDistributionSet(distributionSet.getId(), Pageable.unpaged())
.getContent())
.hasSize(updateTargets.size())
.allMatch(assignedTarget -> updateTargets.stream()
.anyMatch(updateTarget -> updateTarget.getId().equals(assignedTarget.getId())))
.noneMatch(assignedTarget -> readTargets.stream()
.anyMatch(updateTarget -> updateTarget.getId().equals(assignedTarget.getId())))
.noneMatch(assignedTarget -> hiddenTargets.stream()
.anyMatch(updateTarget -> updateTarget.getId().equals(assignedTarget.getId())));
});
}
}

View File

@@ -0,0 +1,331 @@
/**
* Copyright (c) 2025 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.acm;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.eclipse.hawkbit.im.authentication.SpPermission.CREATE_ROLLOUT;
import static org.eclipse.hawkbit.im.authentication.SpPermission.CREATE_TARGET;
import static org.eclipse.hawkbit.im.authentication.SpPermission.DELETE_TARGET;
import static org.eclipse.hawkbit.im.authentication.SpPermission.HANDLE_ROLLOUT;
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_DISTRIBUTION_SET;
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_ROLLOUT;
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_TARGET;
import static org.eclipse.hawkbit.im.authentication.SpPermission.UPDATE_TARGET;
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.runAs;
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.withUser;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.assertj.core.api.Assertions;
import org.eclipse.hawkbit.repository.Identifiable;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TargetManagement.Update;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.springframework.data.domain.Pageable;
class TargetManagementTest extends AbstractAccessControllerManagementTest {
@Test
void verifyCreation() {
// permissions to read all and create only type1 targets
runAs(withAuthorities(READ_TARGET, CREATE_TARGET + "/type.id==" + targetType1.getId()), () -> {
testdataFactory.createTarget("controller_1_create", "Controller-1_create", targetType1);
assertThatThrownBy(() -> testdataFactory.createTarget("controller_2_create", "Controller-2_create", targetType2))
.isInstanceOf(InsufficientPermissionException.class);
});
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Test
void verifyRead() {
assertThat(assignDistributionSet(ds2Type2, List.of(target1Type1, target2Type2, target3Type2)).getAssigned()).isEqualTo(3);
final Target target1Type1 = targetManagement.get(super.target1Type1.getId());
final Target target2Type2 = targetManagement.get(super.target2Type2.getId());
runAs(withAuthorities(READ_TARGET + "/type.id==" + targetType1.getId(), READ_DISTRIBUTION_SET), () -> {
Assertions.<Target> assertThat(targetManagement.findAll(UNPAGED)).containsExactly(target1Type1);
assertThat(targetManagement.count()).isEqualTo(1);
Assertions.<Target> assertThat(targetManagement.get(List.of(target1Type1.getId()))).containsExactly(target1Type1);
final List<Long> allTargetIds = List.of(target1Type1.getId(), target2Type2.getId());
assertThatExceptionOfType(EntityNotFoundException.class).isThrownBy(() -> targetManagement.get(allTargetIds));
assertThat(((TargetManagement) targetManagement).find(target1Type1.getId())).hasValue(target1Type1);
assertThat(targetManagement.find(target2Type2.getId())).isEmpty();
assertThat(targetManagement.findByControllerId(List.of(target1Type1.getControllerId()))).containsExactly(target1Type1);
assertThat(targetManagement.getByControllerId(target1Type1.getControllerId())).isEqualTo(target1Type1);
assertThat(targetManagement.findByControllerId(target1Type1.getControllerId())).hasValue(target1Type1);
final String target2Type2ControllerId = target2Type2.getControllerId();
assertThatThrownBy(() -> targetManagement.getByControllerId(target2Type2ControllerId))
.isInstanceOf(InsufficientPermissionException.class);
assertThatThrownBy(() -> targetManagement.findByControllerId(target2Type2ControllerId))
.isInstanceOf(InsufficientPermissionException.class);
assertThat(targetManagement.getControllerAttributes(target1Type1.getControllerId())).isEmpty();
assertThatThrownBy(() -> targetManagement.getControllerAttributes(target2Type2ControllerId))
.isInstanceOf(InsufficientPermissionException.class);
assertThat(targetManagement.getByControllerId(target1Type1.getControllerId())).isEqualTo(target1Type1);
assertThat(targetManagement.findByControllerId(target1Type1.getControllerId())).hasValue(target1Type1);
assertThatThrownBy(() -> targetManagement.getByControllerId(target2Type2ControllerId))
.isInstanceOf(InsufficientPermissionException.class);
assertThatThrownBy(() -> targetManagement.findByControllerId(target2Type2ControllerId))
.isInstanceOf(InsufficientPermissionException.class);
assertThat(targetManagement.countByRsql("id==*")).isEqualTo(1);
assertThat(targetManagement.countByRsql("id==%s".formatted(target1Type1.getControllerId()))).isEqualTo(1);
assertThat(targetManagement.countByRsql("id==%s".formatted(target2Type2.getControllerId()))).isZero();
Assertions.<Target> assertThat(targetManagement.findByRsql("id==*", UNPAGED))
.hasSize(1)
.containsExactly(target1Type1)
.doesNotContain(target2Type2);
Assertions.<Target> assertThat(
targetManagement.findByRsql("id==%s".formatted(target1Type1.getControllerId()), UNPAGED))
.hasSize(1)
.containsExactly(target1Type1)
.doesNotContain(target2Type2);
assertThat(targetManagement.findByRsql("id==%s".formatted(target2Type2.getControllerId()), UNPAGED)).isEmpty();
assertThat(targetManagement.findByAssignedDistributionSet(ds2Type2.getId(), UNPAGED))
.extracting(Identifiable::getId).containsExactly(target1Type1.getId());
assertThat(targetManagement.findByAssignedDistributionSetAndRsql(ds2Type2.getId(), "id==*", UNPAGED))
.extracting(Identifiable::getId).containsExactly(target1Type1.getId());
assertThat(targetManagement.countByRsqlAndCompatible("id==*", ds2Type2.getType().getId())).isEqualTo(1);
assertThat(targetManagement.getMetadata(target1Type1.getControllerId())).isEmpty();
final String target2Type2ControllerId1 = target2Type2.getControllerId();
assertThatThrownBy(() -> targetManagement.getMetadata(target2Type2ControllerId1))
.isInstanceOf(InsufficientPermissionException.class);
// find by tags
assertThat(targetManagement.findByTag(targetType2.getId(), UNPAGED))
.extracting(Identifiable::getId)
.containsOnly(target1Type1.getId());
assertThat(targetManagement.findByRsqlAndTag("id==*", targetType2.getId(), UNPAGED))
.extracting(Identifiable::getId)
.containsOnly(target1Type1.getId());
});
}
@Disabled
@Test
void verifyReadRolloutRelated() {
assertThat(assignDistributionSet(ds2Type2, List.of(target1Type1, target2Type2, target3Type2)).getAssigned()).isEqualTo(3);
prepareFinishedUpdates(ds2Type2, target1Type1, target2Type2);
final Target target1Type1 = targetManagement.get(super.target1Type1.getId());
runAs(withAuthorities(
READ_TARGET + "/type.id==" + targetType1.getId(),
READ_DISTRIBUTION_SET,
CREATE_ROLLOUT, READ_ROLLOUT, HANDLE_ROLLOUT), () -> {
assertThat(targetManagement.findByInstalledDistributionSet(ds2Type2.getId(), UNPAGED))
.extracting(Identifiable::getId).containsExactly(target1Type1.getId());
assertThat(targetManagement.findByInstalledDistributionSetAndRsql(ds2Type2.getId(), "id==*", UNPAGED))
.extracting(Identifiable::getId).containsExactly(target1Type1.getId());
assertThat(targetManagement.findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(
ds2Type2.getId(), "id==*", UNPAGED))
.hasSize(1)
.extracting(Identifiable::getId).containsOnly(target1Type1.getId());
assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(
target1Type1.getControllerId(), ds2Type2.getId(), "id==*")).isTrue();
assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(
target2Type2.getControllerId(), ds2Type2.getId(), "id==*")).isFalse();
assertThat(targetManagement.findByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(
List.of(1L), "id==*", ds2Type2.getType(), UNPAGED))
.hasSize(1)
.extracting(Identifiable::getId).containsOnly(target1Type1.getId());
assertThat(targetManagement.countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable("id==*", List.of(1L),
ds2Type2.getType())).isEqualTo(1);
final Rollout rollout = testdataFactory.createRolloutByVariables(
"testRollout", "testDescription", 3, "id==*", ds2Type2, "50", "5");
final List<Long> foundTargetIds = rolloutGroupManagement.findByRollout(rollout.getId(), UNPAGED).getContent().stream()
.flatMap(rolloutGroup -> targetManagement.findByInRolloutGroupWithoutAction(rolloutGroup.getId(), UNPAGED)
.getContent().stream()).map(Identifiable::getId).toList();
assertThat(foundTargetIds).hasSize(1).contains(target1Type1.getId());
assertThat(targetManagement.findByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(
List.of(1L), "id==*", ds2Type2.getType(), UNPAGED))
.hasSize(1)
.extracting(Identifiable::getId).containsOnly(target1Type1.getId());
assertThat(targetManagement.countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable("id==*", List.of(1L), ds2Type2.getType()))
.isEqualTo(1);
final Rollout runAsSystem = systemSecurityContext.runAsSystem(
() -> testdataFactory.createRolloutByVariables("testRollout", "testDescription", 3, "id==*", ds2Type2, "50", "5"));
assertThat(rolloutGroupManagement.findByRollout(runAsSystem.getId(), UNPAGED).getContent().stream()
.flatMap(rolloutGroup -> targetManagement.findByInRolloutGroupWithoutAction(rolloutGroup.getId(), UNPAGED)
.getContent().stream()).map(Identifiable::getId).toList())
.hasSize(3);
});
}
@Test
void verifyUpdate() {
final TargetTag testTag = testdataFactory.createTargetTags(1, "testTag").get(0);
final Long testTagId = testTag.getId();
runAs(withAuthorities(
READ_TARGET, UPDATE_TARGET + "/type.id==" + targetType1.getId(),
READ_DISTRIBUTION_SET),
() -> {
assertThat(targetManagement.update(
Update.builder().id(target1Type1.getId()).name("myNewName").build()))
.extracting(NamedEntity::getName).isEqualTo("myNewName");
final Update targetUpdate = Update.builder().id(target2Type2.getId()).name("myNewName").build();
assertThatThrownBy(() -> targetManagement.update(targetUpdate)).isInstanceOf(InsufficientPermissionException.class);
final String metadataKey = "key.dot";
final Map<String, String> metadata = Map.of(metadataKey, "value.dot");
final String target1Type1ControllerId = target1Type1.getControllerId();
targetManagement.createMetadata(target1Type1ControllerId, metadata);
final String target2Type2ControllerId = target2Type2.getControllerId();
assertThatThrownBy(() -> targetManagement.createMetadata(target2Type2ControllerId, metadata))
.isInstanceOf(InsufficientPermissionException.class);
final String newValue = "newValue";
targetManagement.createMetadata(target1Type1ControllerId, metadataKey, newValue);
assertThat(targetManagement.getMetadata(target1Type1ControllerId)).containsEntry(metadataKey, newValue);
assertThatThrownBy(() -> targetManagement.createMetadata(target2Type2ControllerId, metadataKey, newValue))
.isInstanceOf(InsufficientPermissionException.class);
targetManagement.deleteMetadata(target1Type1ControllerId, metadataKey);
assertThatThrownBy(() -> targetManagement.deleteMetadata(target2Type2ControllerId, metadataKey))
.isInstanceOf(EntityNotFoundException.class);
// tag assignments
assertThat(targetManagement.assignTag(List.of(target1Type1.getControllerId()), testTagId))
.extracting(Identifiable::getId).containsExactly(target1Type1.getId());
final List<String> target2Type2ControllerIdList = List.of(target2Type2.getControllerId());
assertThatThrownBy(() -> targetManagement.assignTag(target2Type2ControllerIdList, testTagId))
.isInstanceOf(InsufficientPermissionException.class);
assertThatThrownBy(() -> targetManagement.assignTag(target2Type2ControllerIdList, testTagId))
.isInstanceOf(InsufficientPermissionException.class);
assertThat(targetManagement.unassignTag(List.of(target1Type1.getControllerId()), testTagId)).element(0)
.extracting(Identifiable::getId).isEqualTo(target1Type1.getId());
assertThatThrownBy(() -> targetManagement.unassignTag(target2Type2ControllerIdList, testTagId))
.isInstanceOf(InsufficientPermissionException.class);
assertThatThrownBy(() -> targetManagement.unassignTag(target2Type2ControllerIdList, testTagId))
.isInstanceOf(InsufficientPermissionException.class);
// type
final Long targetType2Id = targetType2.getId();
assertThatThrownBy(() -> targetManagement.assignType(target2Type2ControllerId, targetType2Id))
.isInstanceOf(InsufficientPermissionException.class);
assertThatThrownBy(() -> targetManagement.unassignType(target2Type2ControllerId))
.isInstanceOf(InsufficientPermissionException.class);
final String noPermissionsTestDataControllerId = target2Type2.getControllerId();
assertThatThrownBy(() -> targetManagement.unassignType(noPermissionsTestDataControllerId))
.isInstanceOf(InsufficientPermissionException.class);
assertThat(assignDistributionSet(ds2Type2.getId(), target1Type1.getControllerId()).getAssigned()).isEqualTo(1);
assertThatThrownBy(() -> assignDistributionSet(ds2Type2.getId(), target2Type2ControllerId)).isInstanceOf(
AssertionError.class);
// bunch assignment skips denied since at least one target without update permissions is present
assertThat(assignDistributionSet(
ds3Type2.getId(), List.of(target1Type1.getControllerId(), target2Type2ControllerId), Action.ActionType.FORCED)
.getAssigned()).isEqualTo(1);
assertThat(targetManagement.findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(ds1Type1.getId(), "id==*", UNPAGED))
.extracting(Identifiable::getId).containsExactly(target1Type1.getId());
assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(
target1Type1.getControllerId(), ds1Type1.getId(), "id==*")).isTrue();
assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(
target2Type2.getControllerId(), ds1Type1.getId(), "id==*")).isFalse();
});
}
@Test
void verifyDeletion() {
runAs(withAuthorities(READ_TARGET, DELETE_TARGET + "/type.id==" + targetType1.getId()), () -> {
final List<Long> allTargetIds = List.of(target1Type1.getId(), target2Type2.getId());
assertThatThrownBy(() -> targetManagement.delete(allTargetIds)).isInstanceOf(InsufficientPermissionException.class);
final List<Long> target2Type2IdList = List.of(target2Type2.getId());
assertThatThrownBy(() -> targetManagement.delete(target2Type2IdList)).isInstanceOf(InsufficientPermissionException.class);
targetManagement.delete(List.of(target1Type1.getId()));
});
}
private void prepareFinishedUpdates(final DistributionSet ds, final Target... targets) {
final List<String> controllerIds = Arrays.stream(targets).map(Target::getControllerId).toList();
getFirstAssignedTarget(assignDistributionSet(ds.getId(), controllerIds, Action.ActionType.FORCED));
controllerIds.stream().map(controllerId -> deploymentManagement.findActiveActionsByTarget(controllerId, PAGE).getContent().get(0))
.forEach(action -> {
if (action.getStatus() == Action.Status.WAIT_FOR_CONFIRMATION) {
confirmationManagement.confirmAction(action.getId(), null, null);
}
action = controllerManagement.addUpdateActionStatus(
Action.ActionStatusCreate.builder().actionId(action.getId()).status(Action.Status.RUNNING).build());
controllerManagement.addUpdateActionStatus(
Action.ActionStatusCreate.builder().actionId(action.getId()).status(Action.Status.FINISHED).build());
});
}
/**
* Verifies only manageable targets are part of the rollout
*/
@Test
void verifyRolloutTargetScope() {
final DistributionSet ds = testdataFactory.createDistributionSet("myDs");
distributionSetManagement.lock(ds);
final String[] updateTargetControllerIds = { "update1", "update2", "update3" };
final List<Target> updateTargets = testdataFactory.createTargets(updateTargetControllerIds);
final String[] readTargetControllerIds = { "read1", "read2", "read3", "read4" };
final List<Target> readTargets = testdataFactory.createTargets(readTargetControllerIds);
final List<Target> hiddenTargets = testdataFactory.createTargets("hidden1", "hidden2", "hidden3", "hidden4", "hidden5");
runAs(withUser("user",
READ_DISTRIBUTION_SET,
READ_TARGET + "/controllerId=in=(" + String.join(", ", List.of(updateTargetControllerIds)) + ")" +
" or controllerId=in=(" + String.join(", ", List.of(readTargetControllerIds)) + ")",
UPDATE_TARGET + "/controllerId=in=(" + String.join(", ", List.of(updateTargetControllerIds)) + ")",
CREATE_ROLLOUT, READ_ROLLOUT), () -> {
final Rollout rollout = testdataFactory.createRolloutByVariables(
"testRollout", "description", updateTargets.size(), "id==*", ds, "50", "5");
assertThat(rollout.getTotalTargets()).isEqualTo(updateTargets.size());
final List<RolloutGroup> content = rolloutGroupManagement.findByRollout(rollout.getId(), Pageable.unpaged()).getContent();
assertThat(content).hasSize(updateTargets.size());
final List<Target> rolloutTargets = content.stream().flatMap(
group -> rolloutGroupManagement.findTargetsOfRolloutGroup(group.getId(), Pageable.unpaged()).get())
.toList();
assertThat(rolloutTargets).hasSize(updateTargets.size()).allMatch(
target -> updateTargets.stream().anyMatch(readTarget -> readTarget.getId().equals(target.getId())))
.noneMatch(target -> readTargets.stream()
.anyMatch(readTarget -> readTarget.getId().equals(target.getId())))
.noneMatch(target -> hiddenTargets.stream()
.anyMatch(readTarget -> readTarget.getId().equals(target.getId())));
});
}
}

View File

@@ -1,143 +0,0 @@
/**
* Copyright (c) 2023 Bosch.IO GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.acm;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.eclipse.hawkbit.im.authentication.SpPermission.DELETE_TARGET_TYPE;
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_TARGET_TYPE;
import static org.eclipse.hawkbit.im.authentication.SpPermission.UPDATE_TARGET_TYPE;
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.runAs;
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.withUser;
import java.util.Arrays;
import java.util.List;
import org.eclipse.hawkbit.repository.Identifiable;
import org.eclipse.hawkbit.repository.TargetTypeManagement.Create;
import org.eclipse.hawkbit.repository.TargetTypeManagement.Update;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.junit.jupiter.api.Test;
import org.springframework.data.domain.Pageable;
import org.springframework.test.context.TestPropertySource;
/**
* Feature: Component Tests - Access Control<br/>
* Story: Test Target Type Access Controller
*/
@TestPropertySource(properties = "hawkbit.acm.access-controller.enabled=true")
class TargetTypeAccessControllerTest extends AbstractJpaIntegrationTest {
/**
* Verifies read access rules for target types
*/
@Test
void verifyTargetTypeReadOperations() {
final TargetType permittedTargetType = targetTypeManagement.create(Create.builder().name("type1").build());
final TargetType hiddenTargetType = targetTypeManagement.create(Create.builder().name("type2").build());
runAs(withUser("user", READ_TARGET_TYPE + "/id==" + permittedTargetType.getId()), () -> {
// verify targetTypeManagement#findAll
assertThat(targetTypeManagement.findAll(Pageable.unpaged()).get().map(Identifiable::getId).toList())
.containsExactly(permittedTargetType.getId());
// verify targetTypeManagement#findByRsql
assertThat(targetTypeManagement.findByRsql("name==*", Pageable.unpaged()).get().map(Identifiable::getId).toList())
.containsExactly(permittedTargetType.getId());
// verify targetTypeManagement#count
assertThat(targetTypeManagement.count()).isEqualTo(1);
// verify targetTypeManagement#get by id
assertThat(targetTypeManagement.find(permittedTargetType.getId())).isPresent();
final Long hiddenTargetTypeId = hiddenTargetType.getId();
assertThat(targetTypeManagement.find(hiddenTargetTypeId)).isEmpty();
// verify targetTypeManagement#get by ids
final List<Long> allEntityIds = Arrays.asList(permittedTargetType.getId(), hiddenTargetTypeId);
assertThatExceptionOfType(EntityNotFoundException.class).isThrownBy(() -> targetTypeManagement.get(allEntityIds));
// verify targetTypeManagement#update is not possible. Assert exception thrown.
final Update targetTypeUpdate = Update.builder()
.id(hiddenTargetTypeId).name(hiddenTargetType.getName() + "/new").description("newDesc")
.build();
assertThatThrownBy(() -> targetTypeManagement.update(targetTypeUpdate))
.as("Target type update shouldn't be allowed since the target type is not visible.")
.isInstanceOf(InsufficientPermissionException.class);
// verify targetTypeManagement#delete is not possible. Assert exception thrown.
assertThatThrownBy(() -> targetTypeManagement.delete(hiddenTargetTypeId))
.as("Target type delete shouldn't be allowed since the target type is not visible.")
.isInstanceOf(InsufficientPermissionException.class);
});
}
/**
* Verifies delete access rules for target types
*/
@Test
void verifyTargetTypeDeleteOperations() {
final TargetType manageableTargetType = targetTypeManagement.create(Create.builder().name("type1").build());
final TargetType readOnlyTargetType = targetTypeManagement.create(Create.builder().name("type2").build());
runAs(withUser("user",
READ_TARGET_TYPE + "/id==" + manageableTargetType.getId() + " or id==" + readOnlyTargetType.getId(),
DELETE_TARGET_TYPE + "/id==" + manageableTargetType.getId()), () -> {
// delete the manageableTargetType
targetTypeManagement.delete(manageableTargetType.getId());
// verify targetTypeManagement#delete for readOnlyTargetType is not possible
final Long readOnlyTargetTypeId = readOnlyTargetType.getId();
assertThatThrownBy(() -> targetTypeManagement.delete(readOnlyTargetTypeId))
.isInstanceOfAny(InsufficientPermissionException.class, EntityNotFoundException.class);
});
}
/**
* Verifies update operation for target types
*/
@Test
void verifyTargetTypeUpdateOperations() {
final TargetType manageableTargetType = targetTypeManagement.create(Create.builder().name("type1").build());
final TargetType readOnlyTargetType = targetTypeManagement.create(Create.builder().name("type2").build());
runAs(withUser("user",
READ_TARGET_TYPE + "/id==" + manageableTargetType.getId() + " or id==" + readOnlyTargetType.getId(),
UPDATE_TARGET_TYPE + "/id==" + manageableTargetType.getId()), () -> {
// update the manageableTargetType
targetTypeManagement.update(Update.builder().id(manageableTargetType.getId())
.name(manageableTargetType.getName() + "/new").description("newDesc").build());
// verify targetTypeManagement#update for readOnlyTargetType is not possible
final Update targetTypeUpdate = Update.builder()
.id(readOnlyTargetType.getId()).name(readOnlyTargetType.getName() + "/new").description("newDesc")
.build();
assertThatThrownBy(() -> targetTypeManagement.update(targetTypeUpdate)).isInstanceOf(InsufficientPermissionException.class);
});
}
/**
* Verifies create operation blocked by controller
*/
@Test
void verifyTargetTypeCreationBlockedByAccessController() {
runAs(withUser("user", READ_TARGET_TYPE, UPDATE_TARGET_TYPE), () -> {
// verify targetTypeManagement#create for any type
final Create targetTypeCreate = Create.builder().name("type1").build();
assertThatThrownBy(() -> targetTypeManagement.create(targetTypeCreate))
.as("Target type create shouldn't be allowed since the target type is not visible.")
.isInstanceOf(InsufficientPermissionException.class);
});
}
}

View File

@@ -0,0 +1,101 @@
/**
* Copyright (c) 2025 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.acm;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.eclipse.hawkbit.im.authentication.SpPermission.CREATE_PREFIX;
import static org.eclipse.hawkbit.im.authentication.SpPermission.DELETE_TARGET_TYPE;
import static org.eclipse.hawkbit.im.authentication.SpPermission.DISTRIBUTION_SET_TYPE;
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_PREFIX;
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_TARGET_TYPE;
import static org.eclipse.hawkbit.im.authentication.SpPermission.TARGET_TYPE;
import static org.eclipse.hawkbit.im.authentication.SpPermission.UPDATE_TARGET_TYPE;
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.runAs;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import org.assertj.core.api.Assertions;
import org.eclipse.hawkbit.repository.TargetTypeManagement.Update;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.junit.jupiter.api.Test;
class TargetTypeManagementTest extends AbstractAccessControllerManagementTest {
@Test
void verifyCreate() {
// permissions to read all and create new type with name 'permitted'
runAs(withAuthorities(READ_TARGET_TYPE, CREATE_PREFIX + TARGET_TYPE + "/name==permitted"), () -> {
assertThat(testdataFactory.findOrCreateTargetType("permitted")).isNotNull();
assertThatThrownBy(() -> testdataFactory.findOrCreateTargetType("not_permitted_2"))
.isInstanceOf(InsufficientPermissionException.class);
});
}
@Test
void verifyRead() {
// permissions to read only type1 target types
runAs(withAuthorities(READ_TARGET_TYPE + "/id==" + targetType1.getId()), () -> {
Assertions.<TargetType> assertThat(targetTypeManagement.findAll(UNPAGED)).containsExactly(targetType1);
assertThat(targetTypeManagement.count()).isEqualTo(1);
Assertions.<TargetType> assertThat(targetTypeManagement.findByRsql("name==*", UNPAGED))
.contains(targetType1).doesNotContain(targetType2);
assertThat((Optional) targetTypeManagement.find(targetType1.getId())).hasValue(targetType1);
assertThat(targetTypeManagement.find(targetType2.getId())).isEmpty();
Assertions.<TargetType> assertThat(targetTypeManagement.get(Collections.singletonList(targetType1.getId())))
.contains(targetType1).doesNotContain(targetType2);
final List<Long> noPermissionsEntitiesIds = Collections.singletonList(targetType2.getId());
assertThatExceptionOfType(EntityNotFoundException.class).isThrownBy(() -> targetTypeManagement.get(noPermissionsEntitiesIds));
final List<Long> allEntitiesIds = List.of(targetType1.getId(), targetType2.getId());
assertThatExceptionOfType(EntityNotFoundException.class).isThrownBy(() -> targetTypeManagement.get(allEntitiesIds));
});
}
@Test
void verifyUpdate() {
runAs(withAuthorities(READ_TARGET_TYPE, UPDATE_TARGET_TYPE + "/id==" + targetType1.getId(), READ_PREFIX + DISTRIBUTION_SET_TYPE),
() -> {
assertThat(targetTypeManagement.update(Update.builder().id(targetType1.getId()).description("anotherDesc").build()))
.extracting(NamedEntity::getDescription).isEqualTo("anotherDesc");
final Update targetTypeUpdate = Update.builder().id(targetType2.getId()).description("anotherDesc").build();
assertThatThrownBy(() -> targetTypeManagement.update(targetTypeUpdate)).isInstanceOf(InsufficientPermissionException.class);
targetTypeManagement.assignCompatibleDistributionSetTypes(targetType1.getId(), List.of(dsType2.getId()));
final Long targetType2Id = targetType2.getId();
assertThatThrownBy(
() -> targetTypeManagement.assignCompatibleDistributionSetTypes(targetType2Id, List.of(dsType2.getId())))
.isInstanceOf(InsufficientPermissionException.class);
targetTypeManagement.unassignDistributionSetType(targetType1.getId(), dsType2.getId());
assertThatThrownBy(() -> targetTypeManagement.unassignDistributionSetType(targetType2Id, dsType2.getId()))
.isInstanceOf(InsufficientPermissionException.class);
});
}
@Test
void verifyDelete() {
// assigned type can't be deleted, so create dedicated ones
final TargetType targetType4 = testdataFactory.findOrCreateTargetType("toBeDeleted");
final Long targetType5Id = testdataFactory.findOrCreateTargetType("toBeDeleted2").getId();
runAs(withAuthorities(READ_TARGET_TYPE, DELETE_TARGET_TYPE + "/id==" + targetType4.getId()), () -> {
targetTypeManagement.delete(targetType4.getId());
assertThatThrownBy(() -> targetTypeManagement.delete(targetType5Id)).isInstanceOf(InsufficientPermissionException.class);
});
}
}

View File

@@ -31,7 +31,7 @@ import org.junit.jupiter.api.Test;
* Feature: Component Tests - Access Control<br/>
* Story: Test Distribution Set Access Controller
*/
class TargetTypeQueryManagementTest extends AbstractAccessControllerTest {
class TargetTypeQueryManagementTest extends AbstractAccessControllerManagementTest {
@Test
void verifyAutoAssignmentRestrictionByDs() {