Refactor hawkbit core and security (#2833)

* Refactor hawkbit core and security

* improve access to the base core features - static
* thus easiear access
* and less boilerplate passing of instances

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

* Refactor context classes

* make JSON context serialization default

* AccessContext

* Split hawkbit-security-core to other modules and remove it

---------

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-11-27 13:07:49 +02:00
committed by GitHub
parent 58dbc32a80
commit f6f62db0ad
274 changed files with 2534 additions and 4458 deletions

View File

@@ -9,44 +9,32 @@
*/
package org.eclipse.hawkbit.repository.test;
import java.util.Collections;
import java.util.Locale;
import java.util.Optional;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.atomic.AtomicLong;
import org.eclipse.hawkbit.ContextAware;
import org.eclipse.hawkbit.artifact.ArtifactStorage;
import org.eclipse.hawkbit.artifact.fs.FileArtifactProperties;
import org.eclipse.hawkbit.artifact.fs.FileArtifactStorage;
import org.eclipse.hawkbit.artifact.urlresolver.PropertyBasedArtifactUrlResolver;
import org.eclipse.hawkbit.artifact.urlresolver.PropertyBasedArtifactUrlResolverProperties;
import org.eclipse.hawkbit.im.authentication.Hierarchy;
import org.eclipse.hawkbit.auth.Hierarchy;
import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.repository.RepositoryConfiguration;
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
import org.eclipse.hawkbit.repository.RolloutStatusCache;
import org.eclipse.hawkbit.repository.event.ApplicationEventFilter;
import org.eclipse.hawkbit.repository.event.EventPublisherHolder;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyResolver;
import org.eclipse.hawkbit.repository.test.util.RolloutTestApprovalStrategy;
import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch;
import org.eclipse.hawkbit.security.DdiSecurityProperties;
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
import org.eclipse.hawkbit.security.SecurityContextSerializer;
import org.eclipse.hawkbit.security.SecurityContextTenantAware;
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
import org.eclipse.hawkbit.security.SpringSecurityAuditorAware;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.TenantAware.DefaultTenantResolver;
import org.eclipse.hawkbit.tenancy.TenantAware.TenantResolver;
import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver;
import org.eclipse.hawkbit.tenancy.configuration.ControllerPollProperties;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationEvent;
@@ -71,7 +59,7 @@ import org.springframework.security.concurrent.DelegatingSecurityContextSchedule
*/
@Configuration
@EnableConfigurationProperties({
DdiSecurityProperties.class, PropertyBasedArtifactUrlResolverProperties.class, FileArtifactProperties.class,
PropertyBasedArtifactUrlResolverProperties.class, FileArtifactProperties.class,
HawkbitSecurityProperties.class, ControllerPollProperties.class, TenantConfigurationProperties.class })
@Profile("test")
@EnableAutoConfiguration
@@ -82,6 +70,10 @@ public class TestConfiguration implements AsyncConfigurer {
@Import(RepositoryConfiguration.class)
static class OverridePropertiesSourceFromRepositoryConfiguration {}
static {
Hierarchy.setRoleHierarchy(RoleHierarchyImpl.fromHierarchy(Hierarchy.DEFAULT));
}
@Override
public Executor getAsyncExecutor() {
return asyncExecutor();
@@ -103,29 +95,11 @@ public class TestConfiguration implements AsyncConfigurer {
}));
}
/**
* Disables caching during test to avoid concurrency failures during test.
*/
@Bean
RolloutStatusCache rolloutStatusCache(final TenantAware tenantAware) {
return new RolloutStatusCache(tenantAware);
}
@Bean
LockRegistry lockRegistry() {
return new DefaultLockRegistry();
}
@Bean
SecurityTokenGenerator securityTokenGenerator() {
return new SecurityTokenGenerator();
}
@Bean
SystemSecurityContext systemSecurityContext(final TenantAware tenantAware) {
return new SystemSecurityContext(tenantAware, RoleHierarchyImpl.fromHierarchy(Hierarchy.DEFAULT));
}
@Bean
ArtifactStorage artifactStorage(final FileArtifactProperties artifactFilesystemProperties) {
return new FileArtifactStorage(artifactFilesystemProperties);
@@ -143,25 +117,6 @@ public class TestConfiguration implements AsyncConfigurer {
return new PropertyBasedArtifactUrlResolver(urlHandlerProperties, "");
}
@Bean
UserAuthoritiesResolver authoritiesResolver() {
return username -> Collections.emptyList();
}
@Bean
TenantResolver tenantResolver() {
return new DefaultTenantResolver();
}
@Bean
ContextAware contextAware(
final UserAuthoritiesResolver authoritiesResolver,
@Autowired(required = false) final SecurityContextSerializer securityContextSerializer,
final TenantResolver tenantResolver) {
// allow spying the security context
return org.mockito.Mockito.spy(new SecurityContextTenantAware(authoritiesResolver, securityContextSerializer, tenantResolver));
}
@Bean(name = AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME)
SimpleApplicationEventMulticaster applicationEventMulticaster(final ApplicationEventFilter applicationEventFilter) {
final SimpleApplicationEventMulticaster simpleApplicationEventMulticaster =
@@ -182,7 +137,7 @@ public class TestConfiguration implements AsyncConfigurer {
@Bean
AuditorAware<String> auditorAware() {
return new SpringSecurityAuditorAware();
return () -> Optional.ofNullable(AccessContext.actor());
}
@Bean

View File

@@ -10,9 +10,9 @@
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.SpRole.CONTROLLER_ROLE;
import static org.eclipse.hawkbit.im.authentication.SpRole.SYSTEM_ROLE;
import static org.eclipse.hawkbit.auth.SpPermission.READ_TENANT_CONFIGURATION;
import static org.eclipse.hawkbit.auth.SpRole.CONTROLLER_ROLE;
import static org.eclipse.hawkbit.auth.SpRole.SYSTEM_ROLE;
import java.io.File;
import java.io.IOException;
@@ -59,6 +59,7 @@ import org.eclipse.hawkbit.repository.TargetTagManagement;
import org.eclipse.hawkbit.repository.TargetTypeManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionStatusCreate;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
@@ -77,8 +78,6 @@ import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.test.TestConfiguration;
import org.eclipse.hawkbit.repository.test.matcher.EventVerifier;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
@@ -164,20 +163,14 @@ public abstract class AbstractIntegrationTest {
@Autowired
protected ArtifactManagement artifactManagement;
@Autowired
protected TenantAware tenantAware;
@Autowired
protected SystemManagement systemManagement;
@Autowired
protected TenantConfigurationManagement tenantConfigurationManagement;
@Autowired
protected RolloutManagement rolloutManagement;
@Autowired
protected RolloutHandler rolloutHandler;
@Autowired
protected RolloutGroupManagement rolloutGroupManagement;
@Autowired
protected SystemSecurityContext systemSecurityContext;
@Autowired
protected ArtifactStorage artifactStorage;
@Autowired
protected QuotaManagement quotaManagement;
@@ -215,21 +208,21 @@ public abstract class AbstractIntegrationTest {
final String description = "Updated description.";
osType = SecurityContextSwitch
.callAsPrivileged(() -> testdataFactory.findOrCreateSoftwareModuleType(TestdataFactory.SM_TYPE_OS));
osType = SecurityContextSwitch.callAsPrivileged(() -> softwareModuleTypeManagement
.asPrivileged(() -> testdataFactory.findOrCreateSoftwareModuleType(TestdataFactory.SM_TYPE_OS));
osType = SecurityContextSwitch.asPrivileged(() -> softwareModuleTypeManagement
.update(SoftwareModuleTypeManagement.Update.builder().id(osType.getId()).description(description).build()));
appType = SecurityContextSwitch.callAsPrivileged(
appType = SecurityContextSwitch.asPrivileged(
() -> testdataFactory.findOrCreateSoftwareModuleType(TestdataFactory.SM_TYPE_APP, Integer.MAX_VALUE));
appType = SecurityContextSwitch.callAsPrivileged(() -> softwareModuleTypeManagement
appType = SecurityContextSwitch.asPrivileged(() -> softwareModuleTypeManagement
.update(SoftwareModuleTypeManagement.Update.builder().id(appType.getId()).description(description).build()));
runtimeType = SecurityContextSwitch
.callAsPrivileged(() -> testdataFactory.findOrCreateSoftwareModuleType(TestdataFactory.SM_TYPE_RT));
runtimeType = SecurityContextSwitch.callAsPrivileged(() -> softwareModuleTypeManagement
.asPrivileged(() -> testdataFactory.findOrCreateSoftwareModuleType(TestdataFactory.SM_TYPE_RT));
runtimeType = SecurityContextSwitch.asPrivileged(() -> softwareModuleTypeManagement
.update(SoftwareModuleTypeManagement.Update.builder().id(runtimeType.getId()).description(description).build()));
standardDsType = SecurityContextSwitch.callAsPrivileged(() -> testdataFactory.findOrCreateDefaultTestDsType());
standardDsType = SecurityContextSwitch.asPrivileged(() -> testdataFactory.findOrCreateDefaultTestDsType());
// publish the reset counter market event to reset the counters after
// setup. The setup is transparent by the test and its @ExpectedEvent
@@ -238,7 +231,6 @@ public abstract class AbstractIntegrationTest {
// ApplicationEventMultiCaster which the TestConfiguration is doing so
// the order of the events keep the same.
EventVerifier.publishResetMarkerEvent(eventPublisher);
}
@AfterEach
@@ -252,6 +244,10 @@ public abstract class AbstractIntegrationTest {
}
}
protected static TenantConfigurationManagement tenantConfigurationManagement() {
return TenantConfigHelper.getTenantConfigurationManagement();
}
/**
* Gets a valid cron expression describing a schedule with a single
* maintenance window, starting specified number of minutes after current
@@ -350,13 +346,14 @@ public abstract class AbstractIntegrationTest {
.actionType(actionType).forceTime(forcedTime).weight(weight).confirmationRequired(confirmationFlowActive)
.build())
.toList();
final List<DistributionSetAssignmentResult> results = deploymentManagement.assignDistributionSets(deploymentRequests);
final List<DistributionSetAssignmentResult> results = deploymentManagement.assignDistributionSets(deploymentRequests, null);
assertThat(results).hasSize(1);
return results.get(0);
}
protected List<DistributionSetAssignmentResult> assignDistributionSets(final List<DeploymentRequest> requests) {
final List<DistributionSetAssignmentResult> distributionSetAssignmentResults = deploymentManagement.assignDistributionSets(requests);
final List<DistributionSetAssignmentResult> distributionSetAssignmentResults =
deploymentManagement.assignDistributionSets(requests, null);
assertThat(distributionSetAssignmentResults).hasSize(requests.size());
return distributionSetAssignmentResults;
}
@@ -370,7 +367,7 @@ public abstract class AbstractIntegrationTest {
}
protected DistributionSetAssignmentResult makeAssignment(final DeploymentRequest request) {
final List<DistributionSetAssignmentResult> results = deploymentManagement.assignDistributionSets(Collections.singletonList(request));
final List<DistributionSetAssignmentResult> results = deploymentManagement.assignDistributionSets(List.of(request), null);
assertThat(results).hasSize(1);
return results.get(0);
}
@@ -413,22 +410,22 @@ public abstract class AbstractIntegrationTest {
}
protected void enableMultiAssignments() {
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED, true);
tenantConfigurationManagement().addOrUpdateConfiguration(TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED, true);
}
protected void enableConfirmationFlow() {
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.USER_CONFIRMATION_ENABLED, true);
tenantConfigurationManagement().addOrUpdateConfiguration(TenantConfigurationKey.USER_CONFIRMATION_FLOW_ENABLED, true);
}
protected void disableConfirmationFlow() {
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.USER_CONFIRMATION_ENABLED, false);
tenantConfigurationManagement().addOrUpdateConfiguration(TenantConfigurationKey.USER_CONFIRMATION_FLOW_ENABLED, false);
}
protected boolean isConfirmationFlowActive() {
return SecurityContextSwitch.getAs(
SecurityContextSwitch.withUser("as_system", READ_TENANT_CONFIGURATION),
() -> tenantConfigurationManagement
.getConfigurationValue(TenantConfigurationKey.USER_CONFIRMATION_ENABLED, Boolean.class)
() -> tenantConfigurationManagement()
.getConfigurationValue(TenantConfigurationKey.USER_CONFIRMATION_FLOW_ENABLED, Boolean.class)
.getValue());
}
@@ -469,16 +466,16 @@ public abstract class AbstractIntegrationTest {
}
protected void enableBatchAssignments() {
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.BATCH_ASSIGNMENTS_ENABLED, true);
tenantConfigurationManagement().addOrUpdateConfiguration(TenantConfigurationKey.BATCH_ASSIGNMENTS_ENABLED, true);
}
protected void disableBatchAssignments() {
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.BATCH_ASSIGNMENTS_ENABLED, false);
tenantConfigurationManagement().addOrUpdateConfiguration(TenantConfigurationKey.BATCH_ASSIGNMENTS_ENABLED, false);
}
protected boolean isConfirmationFlowEnabled() {
return tenantConfigurationManagement
.getConfigurationValue(TenantConfigurationKey.USER_CONFIRMATION_ENABLED, Boolean.class).getValue();
return tenantConfigurationManagement().getConfigurationValue(TenantConfigurationKey.USER_CONFIRMATION_FLOW_ENABLED, Boolean.class)
.getValue();
}
// ensure that next action will get current time millis AFTER got from the previous

View File

@@ -9,14 +9,13 @@
*/
package org.eclipse.hawkbit.repository.test.util;
import java.util.List;
import static org.eclipse.hawkbit.context.AccessContext.asSystem;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.event.EventPublisherHolder;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager.CacheEvictEvent;
import org.springframework.context.ApplicationContext;
import org.springframework.data.domain.PageRequest;
@@ -36,26 +35,21 @@ public class CleanupTestExecutionListener extends AbstractTestExecutionListener
@Override
public void afterTestMethod(@NotNull final TestContext testContext) throws Exception {
SecurityContextSwitch.callAsPrivileged(() -> {
SecurityContextSwitch.asPrivileged(() -> {
final ApplicationContext applicationContext = testContext.getApplicationContext();
clearTestRepository(
applicationContext.getBean(SystemSecurityContext.class),
applicationContext.getBean(SystemManagement.class));
clearTestRepository(applicationContext.getBean(SystemManagement.class));
return null;
});
}
private void clearTestRepository(
final SystemSecurityContext systemSecurityContext,
final SystemManagement systemManagement) {
final List<String> tenants = systemSecurityContext.runAsSystem(() -> systemManagement.findTenants(PAGE).getContent());
tenants.forEach(tenant -> {
private void clearTestRepository(final SystemManagement systemManagement) {
asSystem(() -> systemManagement.forEachTenant(tenant -> {
try {
systemSecurityContext.runAsSystem(() -> systemManagement.deleteTenant(tenant));
asSystem(() -> systemManagement.deleteTenant(tenant));
} catch (final Exception e) {
log.error("Error while delete tenant", e);
}
});
}));
// evict global cache
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new CacheEvictEvent.Default(null, null, null));
}

View File

@@ -21,7 +21,7 @@ import java.util.function.Supplier;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.tenancy.TenantAwareUser;
@@ -56,7 +56,7 @@ public class SecurityContextSwitch {
SecurityContextSwitch.systemManagement = systemManagement;
}
public static <T> T callAsPrivileged(final Callable<T> callable) throws Exception {
public static <T> T asPrivileged(final Callable<T> callable) throws Exception {
createTenant(DEFAULT_TENANT);
return callAs(PRIVILEGED_USER, callable);
}

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.context.AccessContext.asSystem;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
@@ -27,6 +28,7 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.IntStream;
import org.apache.commons.io.IOUtils;
import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.Constants;
import org.eclipse.hawkbit.repository.ControllerManagement;
@@ -74,8 +76,6 @@ import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.context.annotation.Profile;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
@@ -160,8 +160,6 @@ public class TestdataFactory {
private final RolloutManagement rolloutManagement;
private final RolloutHandler rolloutHandler;
private final QuotaManagement quotaManagement;
private final TenantAware tenantAware;
private final SystemSecurityContext systemSecurityContext;
public TestdataFactory(
final ControllerManagement controllerManagement, final ArtifactManagement artifactManagement,
@@ -177,8 +175,7 @@ public class TestdataFactory {
final TargetTagManagement<? extends TargetTag> targetTagManagement,
final DeploymentManagement deploymentManagement,
final RolloutManagement rolloutManagement, final RolloutHandler rolloutHandler,
final QuotaManagement quotaManagement,
final TenantAware tenantAware, final SystemSecurityContext systemSecurityContext) {
final QuotaManagement quotaManagement) {
this.controllerManagement = controllerManagement;
this.softwareModuleManagement = softwareModuleManagement;
this.softwareModuleTypeManagement = softwareModuleTypeManagement;
@@ -195,8 +192,6 @@ public class TestdataFactory {
this.rolloutManagement = rolloutManagement;
this.rolloutHandler = rolloutHandler;
this.quotaManagement = quotaManagement;
this.tenantAware = tenantAware;
this.systemSecurityContext = systemSecurityContext;
}
private static final String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@@ -1289,11 +1284,11 @@ public class TestdataFactory {
}
private void rolloutHandleAll() {
final String tenant = tenantAware.getCurrentTenant();
final String tenant = AccessContext.tenant();
if (tenant == null) {
throw new IllegalStateException("Tenant is null");
throw new IllegalStateException("AccessContext is null");
}
systemSecurityContext.runAsSystem(rolloutHandler::handleAll);
asSystem(rolloutHandler::handleAll);
}
private Rollout reloadRollout(final Rollout rollout) {