Fine-grained permissions (#2535)

* Fine-grained permissions

Adds support for permissions of type <permission>(/<rsql filter scope>)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>

* Apply review fixes

---------

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-07-10 13:51:49 +03:00
committed by GitHub
parent 7e8dd046e0
commit 21581c4ea4
69 changed files with 1492 additions and 1487 deletions

View File

@@ -39,7 +39,7 @@ import org.eclipse.hawkbit.repository.event.remote.CancelTargetAssignmentEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent;
import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration;
import org.eclipse.hawkbit.repository.jpa.JpaRepositoryConfiguration;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.DistributionSet;
@@ -60,13 +60,17 @@ import org.springframework.amqp.support.converter.AbstractJavaTypeMapper;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
@ActiveProfiles({ "test" })
/**
* Feature: Component Tests - Device Management Federation API<br/>
* Story: AmqpMessage Dispatcher Service Test
*/
@SpringBootTest(classes = { RepositoryApplicationConfiguration.class }, webEnvironment = SpringBootTest.WebEnvironment.NONE)
@ActiveProfiles({ "test" })
@SpringBootTest(classes = { JpaRepositoryConfiguration.class }, webEnvironment = SpringBootTest.WebEnvironment.NONE)
@TestPropertySource(properties = {
"spring.main.allow-bean-definition-overriding=true",
"spring.cloud.bus.enabled=true" })
class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
private static final String TENANT = "DEFAULT";

View File

@@ -71,15 +71,14 @@ import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConversionException;
import org.springframework.amqp.support.converter.MessageConverter;
@ExtendWith(MockitoExtension.class)
/**
* Feature: Component Tests - Device Management Federation API<br/>
* Story: AmqpMessage Handler Service Test
*/
@ExtendWith(MockitoExtension.class)
class AmqpMessageHandlerServiceTest {
private static final String FAIL_MESSAGE_AMQP_REJECT_REASON = AmqpRejectAndDontRequeueException.class
.getSimpleName() + " was expected, ";
private static final String FAIL_MESSAGE_AMQP_REJECT_REASON = AmqpRejectAndDontRequeueException.class.getSimpleName() + " was expected, ";
private static final String VIRTUAL_HOST = "vHost";
private static final String TENANT = "DEFAULT";

View File

@@ -31,11 +31,11 @@ import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConversionException;
@ExtendWith(MockitoExtension.class)
/**
* Feature: Component Tests - Device Management Federation API<br/>
* Story: Base Amqp Service Test
*/
@ExtendWith(MockitoExtension.class)
class BaseAmqpServiceTest {
@Mock

View File

@@ -42,7 +42,7 @@ import org.eclipse.hawkbit.integration.listener.ReplyToListener;
import org.eclipse.hawkbit.matcher.SoftwareModuleJsonMatcher;
import org.eclipse.hawkbit.rabbitmq.test.AbstractAmqpIntegrationTest;
import org.eclipse.hawkbit.rabbitmq.test.AmqpTestConfiguration;
import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration;
import org.eclipse.hawkbit.repository.jpa.JpaRepositoryConfiguration;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
@@ -66,7 +66,7 @@ import org.springframework.util.CollectionUtils;
*/
@ContextConfiguration(classes = {
DmfApiConfiguration.class, DmfTestConfiguration.class,
RepositoryApplicationConfiguration.class, AmqpTestConfiguration.class })
JpaRepositoryConfiguration.class, AmqpTestConfiguration.class })
abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpIntegrationTest {
protected static final String TENANT_EXIST = "DEFAULT";
@@ -93,9 +93,9 @@ abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpIntegratio
}
protected <T> T waitUntilIsPresent(final Callable<Optional<T>> callable) {
await().until(() -> SecurityContextSwitch.runAsPrivileged(() -> callable.call().isPresent()));
await().until(() -> SecurityContextSwitch.callAsPrivileged(() -> callable.call().isPresent()));
try {
return SecurityContextSwitch.runAsPrivileged(() -> callable.call().get());
return SecurityContextSwitch.callAsPrivileged(() -> callable.call().get());
} catch (final Exception e) {
return null;
}
@@ -367,7 +367,7 @@ abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpIntegratio
waitUntilIsPresent(() -> controllerManagement.getByControllerId(controllerId));
await().untilAsserted(() -> {
try {
final Map<String, String> controllerAttributes = SecurityContextSwitch.runAsPrivileged(
final Map<String, String> controllerAttributes = SecurityContextSwitch.callAsPrivileged(
() -> targetManagement.getControllerAttributes(controllerId));
assertThat(controllerAttributes).hasSameSizeAs(attributes);
assertThat(controllerAttributes).containsAllEntriesOf(attributes);

View File

@@ -794,7 +794,7 @@ class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpServiceInt
}
private void waitUntil(final Callable<Boolean> callable) {
await().until(() -> SecurityContextSwitch.runAsPrivileged(callable));
await().until(() -> SecurityContextSwitch.callAsPrivileged(callable));
}
private void assertLatestMultiActionMessageContainsInstallMessages(final String controllerId,

View File

@@ -1231,7 +1231,7 @@ class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServiceIntegr
private void assertAction(final Long actionId, final int messages, final Status... expectedActionStates) {
await().untilAsserted(() -> {
try {
SecurityContextSwitch.runAsPrivileged(() -> {
SecurityContextSwitch.callAsPrivileged(() -> {
final List<ActionStatus> actionStatusList = deploymentManagement.findActionStatusByAction(actionId, PAGE).getContent();
// Check correlation ID
@@ -1265,7 +1265,7 @@ class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServiceIntegr
final Status... expectedActionStates) {
await().untilAsserted(() -> {
try {
SecurityContextSwitch.runAsPrivileged(() -> {
SecurityContextSwitch.callAsPrivileged(() -> {
final List<ActionStatus> actionStatusList = deploymentManagement
.findActionStatusByAction(actionId, PAGE).getContent();
assertThat(actionStatusList).hasSize(statusListCount);

View File

@@ -15,7 +15,7 @@ import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.awaitility.Awaitility;
import org.awaitility.core.ConditionFactory;
import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration;
import org.eclipse.hawkbit.repository.jpa.JpaRepositoryConfiguration;
import org.eclipse.hawkbit.repository.test.TestConfiguration;
import org.eclipse.hawkbit.repository.test.util.AbstractIntegrationTest;
import org.junit.jupiter.api.BeforeEach;
@@ -30,12 +30,16 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
@Slf4j
@RabbitAvailable
@ContextConfiguration(classes = { RepositoryApplicationConfiguration.class, AmqpTestConfiguration.class, TestConfiguration.class })
@ContextConfiguration(classes = { JpaRepositoryConfiguration.class, AmqpTestConfiguration.class, TestConfiguration.class })
// Dirty context is necessary to create a new vhost and recreate all necessary beans after every test class.
@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
@TestPropertySource(properties = {
"spring.main.allow-bean-definition-overriding=true",
"spring.cloud.bus.enabled=true" })
@SuppressWarnings("java:S6813") // constructor injects are not possible for test classes
public abstract class AbstractAmqpIntegrationTest extends AbstractIntegrationTest {
@@ -60,6 +64,7 @@ public abstract class AbstractAmqpIntegrationTest extends AbstractIntegrationTes
private static final Duration AT_LEAST = Duration.ofMillis(Integer.getInteger("hawkbit.it.amqp.await.atLeastMs", 100));
private static final Duration POLL_INTERVAL = Duration.ofMillis(Integer.getInteger("hawkbit.it.amqp.await.pollIntervalMs", 200));
private static final Duration TIMEOUT = Duration.ofMillis(Integer.getInteger("hawkbit.it.amqp.await.timeoutMs", 5000));
@Override
protected ConditionFactory await() {
return Awaitility.await().atLeast(AT_LEAST).pollInterval(POLL_INTERVAL).atMost(TIMEOUT);