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

@@ -10,6 +10,7 @@
package org.eclipse.hawkbit.repository.test.util;
import static org.assertj.core.api.Assertions.assertThat;
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_TENANT_CONFIGURATION;
import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.CONTROLLER_ROLE;
import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.SYSTEM_ROLE;
@@ -94,7 +95,6 @@ import org.springframework.test.context.TestPropertySource;
@WithUser(principal = "bumlux", allSpPermissions = true, authorities = { CONTROLLER_ROLE, SYSTEM_ROLE })
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
@ContextConfiguration(classes = { TestConfiguration.class })
//@Import(TestChannelBinderConfiguration.class)
// destroy the context after each test class because otherwise we get problem when context is
// refreshed we e.g. get two instances of CacheManager which leads to very strange test failures.
@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
@@ -176,7 +176,7 @@ public abstract class AbstractIntegrationTest {
@Autowired
protected TestdataFactory testdataFactory;
@Autowired
@Autowired(required = false)
protected ServiceMatcher serviceMatcher;
@Autowired
protected ApplicationEventPublisher eventPublisher;
@@ -203,21 +203,21 @@ public abstract class AbstractIntegrationTest {
final String description = "Updated description.";
osType = SecurityContextSwitch
.runAsPrivileged(() -> testdataFactory.findOrCreateSoftwareModuleType(TestdataFactory.SM_TYPE_OS));
osType = SecurityContextSwitch.runAsPrivileged(() -> softwareModuleTypeManagement
.callAsPrivileged(() -> testdataFactory.findOrCreateSoftwareModuleType(TestdataFactory.SM_TYPE_OS));
osType = SecurityContextSwitch.callAsPrivileged(() -> softwareModuleTypeManagement
.update(entityFactory.softwareModuleType().update(osType.getId()).description(description)));
appType = SecurityContextSwitch.runAsPrivileged(
appType = SecurityContextSwitch.callAsPrivileged(
() -> testdataFactory.findOrCreateSoftwareModuleType(TestdataFactory.SM_TYPE_APP, Integer.MAX_VALUE));
appType = SecurityContextSwitch.runAsPrivileged(() -> softwareModuleTypeManagement
appType = SecurityContextSwitch.callAsPrivileged(() -> softwareModuleTypeManagement
.update(entityFactory.softwareModuleType().update(appType.getId()).description(description)));
runtimeType = SecurityContextSwitch
.runAsPrivileged(() -> testdataFactory.findOrCreateSoftwareModuleType(TestdataFactory.SM_TYPE_RT));
runtimeType = SecurityContextSwitch.runAsPrivileged(() -> softwareModuleTypeManagement
.callAsPrivileged(() -> testdataFactory.findOrCreateSoftwareModuleType(TestdataFactory.SM_TYPE_RT));
runtimeType = SecurityContextSwitch.callAsPrivileged(() -> softwareModuleTypeManagement
.update(entityFactory.softwareModuleType().update(runtimeType.getId()).description(description)));
standardDsType = SecurityContextSwitch.runAsPrivileged(() -> testdataFactory.findOrCreateDefaultTestDsType());
standardDsType = SecurityContextSwitch.callAsPrivileged(() -> testdataFactory.findOrCreateDefaultTestDsType());
// publish the reset counter market event to reset the counters after
// setup. The setup is transparent by the test and its @ExpectedEvent
@@ -267,6 +267,7 @@ public abstract class AbstractIntegrationTest {
private static final Duration AT_LEAST = Duration.ofMillis(Integer.getInteger("hawkbit.it.rest.await.atLeastMs", 5));
private static final Duration POLL_INTERVAL = Duration.ofMillis(Integer.getInteger("hawkbit.it.rest.await.pollIntervalMs", 10));
private static final Duration TIMEOUT = Duration.ofMillis(Integer.getInteger("hawkbit.it.rest.await.timeoutMs", 200));
// default wait condition factory
protected ConditionFactory await() {
return Awaitility.await().atLeast(AT_LEAST).pollInterval(POLL_INTERVAL).atMost(TIMEOUT);
@@ -413,7 +414,11 @@ public abstract class AbstractIntegrationTest {
}
protected boolean isConfirmationFlowActive() {
return tenantConfigurationManagement.getConfigurationValue(TenantConfigurationKey.USER_CONFIRMATION_ENABLED, Boolean.class).getValue();
return SecurityContextSwitch.getAs(
SecurityContextSwitch.withUser("as_system", READ_TENANT_CONFIGURATION),
() -> tenantConfigurationManagement
.getConfigurationValue(TenantConfigurationKey.USER_CONFIRMATION_ENABLED, Boolean.class)
.getValue());
}
protected Long getOsModule(final DistributionSet ds) {

View File

@@ -25,7 +25,7 @@ public class CleanupTestExecutionListener extends AbstractTestExecutionListener
@Override
public void afterTestMethod(final TestContext testContext) throws Exception {
SecurityContextSwitch.runAsPrivileged(() -> {
SecurityContextSwitch.callAsPrivileged(() -> {
final ApplicationContext applicationContext = testContext.getApplicationContext();
new JpaTestRepositoryManagement(applicationContext.getBean(TenantAwareCacheManager.class),
applicationContext.getBean(SystemSecurityContext.class),

View File

@@ -16,6 +16,7 @@ import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.function.Supplier;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@@ -35,12 +36,12 @@ public class SecurityContextSwitch {
private static final WithUser PRIVILEDGED_USER =
createWithUser("bumlux", DEFAULT_TENANT, false, true, false, "ROLE_CONTROLLER", "ROLE_SYSTEM_CODE");
public static <T> T runAsPrivileged(final Callable<T> callable) throws Exception {
public static <T> T callAsPrivileged(final Callable<T> callable) throws Exception {
createTenant(DEFAULT_TENANT);
return runAs(PRIVILEDGED_USER, callable);
return callAs(PRIVILEDGED_USER, callable);
}
public static <T> T runAs(final WithUser withUser, final Callable<T> callable) throws Exception {
public static <T> T callAs(final WithUser withUser, final Callable<T> callable) throws Exception {
final SecurityContext oldContext = SecurityContextHolder.getContext();
setSecurityContext(withUser);
if (withUser.autoCreateTenant()) {
@@ -53,6 +54,23 @@ public class SecurityContextSwitch {
}
}
public static <T> T getAs(final WithUser withUser, final Supplier<T> supplier) {
try {
return callAs(withUser, supplier::get);
} catch (final RuntimeException e) {
throw e;
} catch (final Exception e) {
throw new IllegalStateException("Failed to handle all rollouts", e);
}
}
public static void runAs(final WithUser withUser, final Runnable runnable) {
getAs(withUser, (Supplier<? extends Object>) () -> {
runnable.run();
return null;
});
}
public static WithUser withController(final String principal, final String... authorities) {
return withUserAndTenant(principal, DEFAULT_TENANT, true, false, true, authorities);
}

View File

@@ -10,6 +10,7 @@
package org.eclipse.hawkbit.repository.test.util;
import static org.assertj.core.api.Assertions.assertThat;
import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.SYSTEM_ROLE;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
@@ -1018,7 +1019,7 @@ public class TestdataFactory {
groupSize, confirmationRequired, conditions, dynamicRolloutGroupTemplate);
// Run here, because Scheduler is disabled during tests
rolloutHandler.handleAll();
rolloutHandleAll();
return rolloutManagement.get(rollout.getId()).get();
}
@@ -1127,9 +1128,9 @@ public class TestdataFactory {
public Rollout createSoftDeletedRollout(final String prefix) {
final Rollout newRollout = createRollout(prefix);
rolloutManagement.start(newRollout.getId());
rolloutHandler.handleAll();
rolloutHandleAll();
rolloutManagement.delete(newRollout.getId());
rolloutHandler.handleAll();
rolloutHandleAll();
return newRollout;
}
@@ -1247,17 +1248,20 @@ public class TestdataFactory {
}
private Action sendUpdateActionStatusToTarget(final Status status, final Action updActA, final Collection<String> msgs) {
return controllerManagement.addUpdateActionStatus(
entityFactory.actionStatus().create(updActA.getId()).status(status).messages(msgs));
return controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(updActA.getId()).status(status).messages(msgs));
}
private Rollout startAndReloadRollout(final Rollout rollout) {
rolloutManagement.start(rollout.getId());
// Run here, because scheduler is disabled during tests
rolloutHandler.handleAll();
rolloutHandleAll();
return reloadRollout(rollout);
}
private void rolloutHandleAll() {
SecurityContextSwitch.runAs(SecurityContextSwitch.withUser("system", SYSTEM_ROLE), rolloutHandler::handleAll);
}
private Rollout reloadRollout(final Rollout rollout) {
return rolloutManagement.get(rollout.getId()).orElseThrow(NoSuchElementException::new);
}

View File

@@ -79,3 +79,6 @@ hawkbit.server.security.dos.maxActionsPerTarget=20
# Quota - END
# Properties that are managed by autoconfigure module at runtime and not available during test - END
# disable spring cloud bus for tests
spring.cloud.bus.enabled=false