Fix EntityMatcher when for Identifiable.getId (#2724)

* Fix EntityMatcher to process properly filters of type targetType.id - to resolve correctly the getter return type Long not T
* Add AutoAsssignTest access control test
* Simplify rest of the ACM tests

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-10-07 15:26:04 +03:00
committed by GitHub
parent 6907931eb6
commit cc36ca8801
18 changed files with 508 additions and 297 deletions

View File

@@ -0,0 +1,79 @@
/**
* 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 java.util.List;
import java.util.Set;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.model.DistributionSet;
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.TargetType;
import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch;
import org.eclipse.hawkbit.repository.test.util.WithUser;
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 {
protected SoftwareModuleType smType1;
protected SoftwareModuleType smType2;
protected SoftwareModule sm1Type1;
protected SoftwareModule sm2Type2;
protected SoftwareModule sm3Type2;
protected DistributionSetType dsType1;
protected DistributionSetType dsType2;
protected DistributionSet ds1Type1;
protected DistributionSet ds2Type2;
protected DistributionSet ds3Type2;
protected TargetType targetType1;
protected TargetType targetType2;
protected Target target1Type1;
protected Target target2Type2;
protected Target target3Type2;
@BeforeEach
@Override
public void beforeAll() throws Exception {
super.beforeAll();
smType1 = testdataFactory.findOrCreateSoftwareModuleType("SmType1");
smType2 = testdataFactory.findOrCreateSoftwareModuleType("SmType2");
sm1Type1 = softwareModuleManagement.lock(testdataFactory.createSoftwareModule(smType1.getKey()));
sm2Type2 = softwareModuleManagement.lock(testdataFactory.createSoftwareModule(smType2.getKey()));
sm3Type2 = softwareModuleManagement.lock(testdataFactory.createSoftwareModule(smType2.getKey()));
dsType1 = testdataFactory.findOrCreateDistributionSetType("DsType1", "DistributionSetType-1", List.of(smType1), List.of());
dsType2 = testdataFactory.findOrCreateDistributionSetType("DsType2", "DistributionSetType-2", List.of(smType2), List.of(smType1));
ds1Type1 = distributionSetManagement.lock(
testdataFactory.createDistributionSet("Ds1Type1", "1.0", dsType1, List.of(sm1Type1)));
ds2Type2 = distributionSetManagement.lock(
testdataFactory.createDistributionSet("Ds2Type2", "1.0", dsType2, List.of(sm2Type2, sm1Type1)));
ds3Type2 = distributionSetManagement.lock(
testdataFactory.createDistributionSet("Ds3Type2", "1.0", dsType2, List.of(sm3Type2, sm1Type1)));
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);
}
protected static WithUser withAuthorities(final String... authorities) {
AuthorityChecker.validateAuthorities(authorities);
return SecurityContextSwitch.withUser("user", authorities);
}
}

View File

@@ -1,25 +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 org.eclipse.hawkbit.security.SecurityContextSerializer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
class AcmTestConfiguration {
@Bean
@ConditionalOnMissingBean
SecurityContextSerializer securityContextSerializer() {
return SecurityContextSerializer.JSON_SERIALIZATION;
}
}

View File

@@ -27,7 +27,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
@ContextConfiguration(classes = { AccessControllerConfiguration.class })
@TestPropertySource(properties = "hawkbit.acm.access-controller.enabled=true")
class ActionAccessControllerTest extends AbstractJpaIntegrationTest {

View File

@@ -0,0 +1,87 @@
/**
* 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_TARGET;
import static org.eclipse.hawkbit.im.authentication.SpPermission.DELETE_TARGET;
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_DISTRIBUTION_SET;
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.callAs;
import java.util.Optional;
import org.eclipse.hawkbit.repository.Identifiable;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement.AutoAssignDistributionSetUpdate;
import org.eclipse.hawkbit.repository.autoassign.AutoAssignExecutor;
import org.eclipse.hawkbit.repository.jpa.autoassign.AutoAssignScheduler;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.integration.support.locks.LockRegistry;
class AutoAssignTest extends AbstractAccessControllerTest {
@Autowired
AutoAssignExecutor autoAssignExecutor;
@Autowired
LockRegistry lockRegistry;
@Test
void verifyOnlyUpdatableTargetsArePartOfAutoAssignmentByScheduler() throws Exception {
// auto assign scheduler apply stored access control context and the context is correctly applied
verifyOnlyUpdatableTargetsArePartOfAutoAssignment(
() -> new AutoAssignScheduler(systemManagement, systemSecurityContext, autoAssignExecutor, lockRegistry, Optional.empty())
.autoAssignScheduler());
}
@Test
void verifyOnlyUpdatableTargetsArePartOfAutoAssignment() throws Exception {
verifyOnlyUpdatableTargetsArePartOfAutoAssignment(autoAssignExecutor::checkAllTargets);
}
@Test
void verifyOnlyUpdatableTargetsWillGetAssignmentOnSingleCheck() throws Exception {
verifyOnlyUpdatableTargetsArePartOfAutoAssignment(() -> {
autoAssignExecutor.checkSingleTarget(target1Type1.getControllerId());
autoAssignExecutor.checkSingleTarget(target2Type2.getControllerId());
autoAssignExecutor.checkSingleTarget(target3Type2.getControllerId());
});
}
private void verifyOnlyUpdatableTargetsArePartOfAutoAssignment(final Runnable assigner) throws Exception {
final TargetFilterQuery targetFilterQuery = callAs(withAuthorities(
CREATE_TARGET,
READ_TARGET + "/controllerid==*",
UPDATE_TARGET + "/type.id==" + targetType2.getId(), // only updatable (i.e. of targetType2) shall be assigned
DELETE_TARGET + "/type.id==" + targetType1.getId(),
READ_DISTRIBUTION_SET + "/type.id==" + dsType2.getId()),
() -> {
final TargetFilterQuery targetFilter = targetFilterQueryManagement
.create(TargetFilterQueryManagement.Create.builder().name("testAutoAssignment").query("controllerid==*").build());
return targetFilterQueryManagement.updateAutoAssignDS(
new AutoAssignDistributionSetUpdate(targetFilter.getId()).ds(ds2Type2.getId()));
});
// do the assignment
assigner.run();
assertThat(targetManagement.findByAssignedDistributionSet(targetFilterQuery.getAutoAssignDistributionSet().getId(), Pageable.unpaged())
.map(Identifiable::getId).toList())
.as("Only updatable targets should be part of the rollout")
// all targets are distribution set type 2 compatible, but since user has UPDATE_TARGET only for targets of type 2
// only target2 and target3 shall be assigned
.containsExactly(target2Type2.getId(), target3Type2.getId());
}
}

View File

@@ -16,7 +16,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import lombok.SneakyThrows;
@@ -26,11 +26,15 @@ import org.eclipse.hawkbit.repository.autoassign.AutoAssignExecutor;
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.security.SecurityContextSerializer;
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.domain.Pageable;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@@ -43,10 +47,10 @@ import org.springframework.test.context.ContextConfiguration;
* Feature: Component Tests - Context runner<br/>
* Story: Test Context Runner
*/
@ContextConfiguration(classes = { AcmTestConfiguration.class })
@ContextConfiguration(classes = { ContextAwareTest.TestConfiguration.class })
class ContextAwareTest extends AbstractJpaIntegrationTest {
private static final List<String> AUTHORITIES = SpPermission.getAllAuthorities();
private static final Set<String> AUTHORITIES = SpPermission.getAllAuthorities();
@Autowired
AutoAssignExecutor autoAssignExecutor;
@@ -169,4 +173,14 @@ class ContextAwareTest extends AbstractJpaIntegrationTest {
SecurityContextHolder.clearContext();
}
}
@Configuration
static class TestConfiguration {
@Bean
@ConditionalOnMissingBean
SecurityContextSerializer securityContextSerializer() {
return SecurityContextSerializer.JSON_SERIALIZATION;
}
}
}

View File

@@ -47,7 +47,6 @@ import org.springframework.test.context.TestPropertySource;
* Feature: Component Tests - Access Control<br/>
* Story: Test Distribution Set Access Controller
*/
@ContextConfiguration(classes = { AccessControllerConfiguration.class })
@TestPropertySource(properties = "hawkbit.acm.access-controller.enabled=true")
class DistributionSetAccessControllerTest extends AbstractJpaIntegrationTest {

View File

@@ -43,14 +43,12 @@ 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.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
/**
* Feature: Component Tests - Access Control<br/>
* Story: Test Target Access Controller
*/
@ContextConfiguration(classes = { AccessControllerConfiguration.class, AcmTestConfiguration.class })
@TestPropertySource(properties = "hawkbit.acm.access-controller.enabled=true")
class TargetAccessControllerTest extends AbstractJpaIntegrationTest {
@@ -185,7 +183,7 @@ class TargetAccessControllerTest extends AbstractJpaIntegrationTest {
*/
@Test
void verifyTargetAssignment() {
final DistributionSet ds = testdataFactory.createDistributionSet("myDs");
final DistributionSet ds = testdataFactory.createDistributionSet("myDs");
distributionSetManagement.lock(ds);
final Target permittedTarget = targetManagement

View File

@@ -30,14 +30,12 @@ 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.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
/**
* Feature: Component Tests - Access Control<br/>
* Story: Test Target Type Access Controller
*/
@ContextConfiguration(classes = { AccessControllerConfiguration.class })
@TestPropertySource(properties = "hawkbit.acm.access-controller.enabled=true")
class TargetTypeAccessControllerTest extends AbstractJpaIntegrationTest {