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

@@ -38,7 +38,7 @@ import org.eclipse.hawkbit.ddi.json.model.DdiConfirmationFeedback;
import org.eclipse.hawkbit.ddi.json.model.DdiProgress;
import org.eclipse.hawkbit.ddi.json.model.DdiResult;
import org.eclipse.hawkbit.ddi.json.model.DdiStatus;
import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration;
import org.eclipse.hawkbit.repository.jpa.JpaRepositoryConfiguration;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Artifact;
@@ -55,7 +55,7 @@ import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
@ContextConfiguration(
classes = { DdiApiConfiguration.class, RestConfiguration.class, RepositoryApplicationConfiguration.class, TestConfiguration.class })
classes = { DdiApiConfiguration.class, RestConfiguration.class, JpaRepositoryConfiguration.class, TestConfiguration.class })
@TestPropertySource(locations = "classpath:/ddi-test.properties")
public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrationTest {

View File

@@ -12,6 +12,10 @@ package org.eclipse.hawkbit.ddi.rest.resource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS;
import static org.eclipse.hawkbit.im.authentication.SpPermission.TENANT_CONFIGURATION;
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.callAs;
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.getAs;
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.withController;
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.withUser;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
@@ -55,7 +59,6 @@ import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.test.matcher.Expect;
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch;
import org.eclipse.hawkbit.repository.test.util.WithUser;
import org.eclipse.hawkbit.rest.util.JsonBuilder;
import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter;
@@ -126,7 +129,7 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
final Target findTargetByControllerID = targetManagement.getByControllerID(knownTargetControllerId).get();
assertThat(findTargetByControllerID.getCreatedBy()).isEqualTo(knownCreatedBy);
// make a poll, audit information should not be changed, run as controller principal!
SecurityContextSwitch.runAs(SecurityContextSwitch.withController("controller", CONTROLLER_ROLE_ANONYMOUS),
callAs(withController("controller", CONTROLLER_ROLE_ANONYMOUS),
() -> {
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), knownTargetControllerId))
.andDo(MockMvcResultPrinter.print())
@@ -198,8 +201,8 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1),
@Expect(type = TenantConfigurationDeletedEvent.class, count = 1) })
void pollWithModifiedGlobalPollingTime() throws Exception {
withPollingTime("00:02:00", () -> SecurityContextSwitch.runAs(
SecurityContextSwitch.withUser("controller", CONTROLLER_ROLE_ANONYMOUS),
withPollingTime("00:02:00", () -> callAs(
withUser("controller", CONTROLLER_ROLE_ANONYMOUS),
() -> {
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), 4711))
.andDo(MockMvcResultPrinter.print())
@@ -221,8 +224,8 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1),
@Expect(type = TenantConfigurationDeletedEvent.class, count = 1) })
void pollWithModifiedWithOverridesGlobalPollingTime() throws Exception {
withPollingTime("00:02:00, controllerid == 4711 -> 00:01:00", () -> SecurityContextSwitch.runAs(
SecurityContextSwitch.withUser("controller", CONTROLLER_ROLE_ANONYMOUS),
withPollingTime("00:02:00, controllerid == 4711 -> 00:01:00", () -> callAs(
withUser("controller", CONTROLLER_ROLE_ANONYMOUS),
() -> {
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), 4711))
.andDo(MockMvcResultPrinter.print())
@@ -363,7 +366,7 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
final String knownControllerId1 = "0815";
final long create = System.currentTimeMillis();
// make a poll, audit information should be set on plug and play
SecurityContextSwitch.runAs(SecurityContextSwitch.withController("controller", CONTROLLER_ROLE_ANONYMOUS),
callAs(withController("controller", CONTROLLER_ROLE_ANONYMOUS),
() -> {
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), knownControllerId1))
.andDo(MockMvcResultPrinter.print())
@@ -582,7 +585,7 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
void sleepTimeResponseForDifferentMaintenanceWindowParameters() throws Exception {
final DistributionSet ds = testdataFactory.createDistributionSet("");
SecurityContextSwitch.runAs(SecurityContextSwitch.withUser("tenantadmin", TENANT_CONFIGURATION),
getAs(withUser("tenantadmin", TENANT_CONFIGURATION),
() -> {
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.POLLING_TIME, "00:05:00");
return null;
@@ -751,7 +754,7 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
}
private void withPollingTime(final String pollingTime, final Callable<Void> runnable) throws Exception {
SecurityContextSwitch.runAs(SecurityContextSwitch.withUser("tenantadmin", TENANT_CONFIGURATION),
getAs(withUser("tenantadmin", TENANT_CONFIGURATION),
() -> {
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.POLLING_TIME, pollingTime);
return null;
@@ -759,7 +762,7 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
try {
runnable.call();
} finally {
SecurityContextSwitch.runAs(SecurityContextSwitch.withUser("tenantadmin", TENANT_CONFIGURATION),
getAs(withUser("tenantadmin", TENANT_CONFIGURATION),
() -> {
tenantConfigurationManagement.deleteConfiguration(TenantConfigurationKey.POLLING_TIME);
return null;

View File

@@ -8,6 +8,10 @@
# SPDX-License-Identifier: EPL-2.0
#
# Logging START - activate to see request/response details
#logging.level.org.eclipse.hawkbit.rest.util.MockMvcResultPrinter=DEBUG
# Logging END
# DDI configuration - START
hawkbit.controller.pollingTime=00:01:00
hawkbit.controller.pollingOverdueTime=00:01:00
@@ -21,6 +25,6 @@ spring.servlet.multipart.max-file-size=5MB
hawkbit.server.security.dos.maxStatusEntriesPerAction=100
hawkbit.server.security.dos.maxAttributeEntriesPerTarget=10
# Quota - END
# Logging START - activate to see request/response details
#logging.level.org.eclipse.hawkbit.rest.util.MockMvcResultPrinter=DEBUG
# Logging END
# disable spring cloud bus for tests
spring.cloud.bus.enabled=false