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,8 +9,11 @@
*/
package org.eclipse.hawkbit.event;
import static org.eclipse.hawkbit.context.AccessContext.asSystemAsTenant;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import io.protostuff.ProtostuffIOUtil;
import io.protostuff.Schema;
import lombok.extern.slf4j.Slf4j;
@@ -19,7 +22,6 @@ import org.eclipse.hawkbit.repository.event.EventPublisherHolder;
import org.eclipse.hawkbit.repository.event.remote.AbstractRemoteEvent;
import org.eclipse.hawkbit.repository.event.remote.RemoteTenantAwareEvent;
import org.eclipse.hawkbit.repository.event.remote.service.AbstractServiceRemoteEvent;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -47,10 +49,9 @@ public class EventPublisherConfiguration {
*/
@Bean(name = AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME)
ApplicationEventMulticaster applicationEventMulticaster(
@Qualifier("asyncExecutor") final Executor executor,
final SystemSecurityContext systemSecurityContext, final ApplicationEventFilter applicationEventFilter) {
@Qualifier("asyncExecutor") final Executor executor, final ApplicationEventFilter applicationEventFilter) {
final SimpleApplicationEventMulticaster simpleApplicationEventMulticaster =
new TenantAwareApplicationEventPublisher(systemSecurityContext, applicationEventFilter);
new TenantAwareApplicationEventPublisher(applicationEventFilter);
simpleApplicationEventMulticaster.setTaskExecutor(executor);
return simpleApplicationEventMulticaster;
}
@@ -76,17 +77,14 @@ public class EventPublisherConfiguration {
private static class TenantAwareApplicationEventPublisher extends SimpleApplicationEventMulticaster {
private final SystemSecurityContext systemSecurityContext;
private final ApplicationEventFilter applicationEventFilter;
protected TenantAwareApplicationEventPublisher(
final SystemSecurityContext systemSecurityContext, final ApplicationEventFilter applicationEventFilter) {
this.systemSecurityContext = systemSecurityContext;
protected TenantAwareApplicationEventPublisher(final ApplicationEventFilter applicationEventFilter) {
this.applicationEventFilter = applicationEventFilter;
}
/**
* Was overridden that not every event has to run within an own tenantAware.
* Was overridden that not every event has to run within an own tenant.
*/
@Override
public void multicastEvent(final ApplicationEvent event, final ResolvableType eventType) {
@@ -95,19 +93,13 @@ public class EventPublisherConfiguration {
}
if (event instanceof final RemoteTenantAwareEvent remoteEvent) {
systemSecurityContext.runAsSystemAsTenant(() -> {
super.multicastEvent(event, eventType);
return null;
}, remoteEvent.getTenant());
asSystemAsTenant(remoteEvent.getTenant(), () -> super.multicastEvent(event, eventType));
return;
}
if (event instanceof final AbstractServiceRemoteEvent<?> serviceRemoteEvent
&& serviceRemoteEvent.getRemoteEvent() instanceof RemoteTenantAwareEvent tenantAwareEvent) {
systemSecurityContext.runAsSystemAsTenant(() -> {
super.multicastEvent(event, eventType);
return null;
}, tenantAwareEvent.getTenant());
asSystemAsTenant(tenantAwareEvent.getTenant(), () -> super.multicastEvent(event, eventType));
return;
}

View File

@@ -26,15 +26,6 @@ import org.eclipse.hawkbit.repository.event.remote.DistributionSetTypeDeletedEve
import org.eclipse.hawkbit.repository.event.remote.DownloadProgressEvent;
import org.eclipse.hawkbit.repository.event.remote.MultiActionAssignEvent;
import org.eclipse.hawkbit.repository.event.remote.MultiActionCancelEvent;
import org.eclipse.hawkbit.repository.event.remote.service.ActionCreatedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.ActionUpdatedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.CancelTargetAssignmentServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.MultiActionAssignServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.MultiActionCancelServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetAssignDistributionSetServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetAttributesRequestedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetCreatedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetDeletedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.RolloutDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.RolloutGroupDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.RolloutStoppedEvent;
@@ -74,6 +65,15 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TargetTypeUpdatedEvent
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TenantConfigurationCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TenantConfigurationUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.service.ActionCreatedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.ActionUpdatedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.CancelTargetAssignmentServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.MultiActionAssignServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.MultiActionCancelServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetAssignDistributionSetServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetAttributesRequestedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetCreatedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetDeletedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetUpdatedServiceEvent;
/**

View File

@@ -12,8 +12,8 @@ package org.eclipse.hawkbit.repository;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.im.authentication.Hierarchy;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.auth.Hierarchy;
import org.eclipse.hawkbit.auth.SpringEvalExpressions;
import org.eclipse.hawkbit.tenancy.configuration.ControllerPollProperties;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
import org.springframework.beans.factory.annotation.Value;
@@ -47,8 +47,7 @@ public class RepositoryConfiguration {
@Bean
@ConditionalOnMissingBean
@SuppressWarnings("java:S3358")
// java:S3358 better readable this way
@SuppressWarnings("java:S3358") // java:S3358 better readable this way
RoleHierarchy roleHierarchy(
// if configured replaces the hierarchy completely
@Value("${hawkbit.hierarchy:}") final String hierarchy,
@@ -62,6 +61,9 @@ public class RepositoryConfiguration {
@Bean
PermissionEvaluator permissionEvaluator(final RoleHierarchy roleHierarchy) {
// sets up global access to the role hierarchy
Hierarchy.setRoleHierarchy(roleHierarchy);
// and returns a custom permission evaluator
return new DenyAllPermissionEvaluator() {
@Override
@@ -72,7 +74,8 @@ public class RepositoryConfiguration {
.replace(SpringEvalExpressions.PERMISSION_GROUP_PLACEHOLDER, permissionSupport.permissionGroup());
// do permissions check
final boolean hasPermission = roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities()).stream()
final boolean hasPermission = Hierarchy.getRoleHierarchy()
.getReachableGrantedAuthorities(authentication.getAuthorities()).stream()
.map(GrantedAuthority::getAuthority)
.anyMatch(authority -> authority.equals(neededPermission));
if (!hasPermission) {

View File

@@ -9,6 +9,8 @@
*/
package org.eclipse.hawkbit.repository;
import static org.eclipse.hawkbit.context.AccessContext.asSystemAsTenant;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -25,7 +27,6 @@ import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.cache.Cache;
import org.springframework.context.event.EventListener;
@@ -39,19 +40,13 @@ public class RolloutStatusCache {
private static final TenantAwareCacheManager CACHE_MANAGER = TenantAwareCacheManager.getInstance();
private final TenantAware tenantAware;
public RolloutStatusCache(final TenantAware tenantAware) {
this.tenantAware = tenantAware;
}
/**
* Retrieves cached list of {@link TotalTargetCountActionStatus} of {@link Rollout}s.
*
* @param rollouts rolloutIds to retrieve cache entries for
* @return map of cached entries
*/
public Map<Long, List<TotalTargetCountActionStatus>> getRolloutStatus(final List<Long> rollouts) {
public static Map<Long, List<TotalTargetCountActionStatus>> getRolloutStatus(final List<Long> rollouts) {
return retrieveFromCache(rollouts, getRolloutStatusCache());
}
@@ -61,7 +56,7 @@ public class RolloutStatusCache {
* @param rolloutId to retrieve cache entries for
* @return map of cached entries
*/
public List<TotalTargetCountActionStatus> getRolloutStatus(final Long rolloutId) {
public static List<TotalTargetCountActionStatus> getRolloutStatus(final Long rolloutId) {
return retrieveFromCache(rolloutId, getRolloutStatusCache());
}
@@ -71,7 +66,7 @@ public class RolloutStatusCache {
* @param rolloutGroups rolloutGroupsIds to retrieve cache entries for
* @return map of cached entries
*/
public Map<Long, List<TotalTargetCountActionStatus>> getRolloutGroupStatus(final List<Long> rolloutGroups) {
public static Map<Long, List<TotalTargetCountActionStatus>> getRolloutGroupStatus(final List<Long> rolloutGroups) {
return retrieveFromCache(rolloutGroups, getGroupStatusCache());
}
@@ -81,7 +76,7 @@ public class RolloutStatusCache {
* @param groupId to retrieve cache entries for
* @return map of cached entries
*/
public List<TotalTargetCountActionStatus> getRolloutGroupStatus(final Long groupId) {
public static List<TotalTargetCountActionStatus> getRolloutGroupStatus(final Long groupId) {
return retrieveFromCache(groupId, getGroupStatusCache());
}
@@ -91,7 +86,7 @@ public class RolloutStatusCache {
*
* @param put map of cached entries
*/
public void putRolloutStatus(final Map<Long, List<TotalTargetCountActionStatus>> put) {
public static void putRolloutStatus(final Map<Long, List<TotalTargetCountActionStatus>> put) {
putIntoCache(put, getRolloutStatusCache());
}
@@ -101,7 +96,7 @@ public class RolloutStatusCache {
* @param rolloutId the cache entries belong to
* @param status list to cache
*/
public void putRolloutStatus(final Long rolloutId, final List<TotalTargetCountActionStatus> status) {
public static void putRolloutStatus(final Long rolloutId, final List<TotalTargetCountActionStatus> status) {
putIntoCache(rolloutId, status, getRolloutStatusCache());
}
@@ -110,7 +105,7 @@ public class RolloutStatusCache {
*
* @param put map of cached entries
*/
public void putRolloutGroupStatus(final Map<Long, List<TotalTargetCountActionStatus>> put) {
public static void putRolloutGroupStatus(final Map<Long, List<TotalTargetCountActionStatus>> put) {
putIntoCache(put, getGroupStatusCache());
}
@@ -120,51 +115,51 @@ public class RolloutStatusCache {
* @param groupId the cache entries belong to
* @param status list to cache
*/
public void putRolloutGroupStatus(final Long groupId, final List<TotalTargetCountActionStatus> status) {
public static void putRolloutGroupStatus(final Long groupId, final List<TotalTargetCountActionStatus> status) {
putIntoCache(groupId, status, getGroupStatusCache());
}
@EventListener(classes = AbstractActionEvent.class)
public void invalidateCachedTotalTargetCountActionStatus(final AbstractActionEvent event) {
if (event.getRolloutId() != null) {
final Cache cache = tenantAware.runAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_RO_NAME));
final Cache cache = asSystemAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_RO_NAME));
cache.evict(event.getRolloutId());
}
if (event.getRolloutGroupId() != null) {
final Cache cache = tenantAware.runAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_GR_NAME));
final Cache cache = asSystemAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_GR_NAME));
cache.evict(event.getRolloutGroupId());
}
}
@EventListener(classes = RolloutDeletedEvent.class)
public void invalidateCachedTotalTargetCountOnRolloutDelete(final RolloutDeletedEvent event) {
final Cache cache = tenantAware.runAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_RO_NAME));
final Cache cache = asSystemAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_RO_NAME));
cache.evict(event.getEntityId());
}
@EventListener(classes = RolloutGroupDeletedEvent.class)
public void invalidateCachedTotalTargetCountOnRolloutGroupDelete(final RolloutGroupDeletedEvent event) {
final Cache cache = tenantAware.runAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_GR_NAME));
final Cache cache = asSystemAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_GR_NAME));
cache.evict(event.getEntityId());
}
@EventListener(classes = RolloutStoppedEvent.class)
public void invalidateCachedTotalTargetCountOnRolloutStopped(final RolloutStoppedEvent event) {
final Cache cache = tenantAware.runAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_RO_NAME));
final Cache cache = asSystemAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_RO_NAME));
cache.evict(event.getRolloutId());
event.getRolloutGroupIds().forEach(
groupId -> tenantAware.runAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_GR_NAME)).evict(groupId));
groupId -> asSystemAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_GR_NAME)).evict(groupId));
}
private @NotNull Map<Long, List<TotalTargetCountActionStatus>> retrieveFromCache(final List<Long> ids, @NotNull final Cache cache) {
private static @NotNull Map<Long, List<TotalTargetCountActionStatus>> retrieveFromCache(final List<Long> ids, @NotNull final Cache cache) {
return ids.stream()
.map(id -> cache.get(id, CachedTotalTargetCountActionStatus.class))
.filter(Objects::nonNull)
.collect(Collectors.toMap(CachedTotalTargetCountActionStatus::id, CachedTotalTargetCountActionStatus::status));
}
private List<TotalTargetCountActionStatus> retrieveFromCache(final Long id, @NotNull final Cache cache) {
private static List<TotalTargetCountActionStatus> retrieveFromCache(final Long id, @NotNull final Cache cache) {
final CachedTotalTargetCountActionStatus cacheItem = cache.get(id, CachedTotalTargetCountActionStatus.class);
if (cacheItem == null) {
return Collections.emptyList();
@@ -172,20 +167,19 @@ public class RolloutStatusCache {
return cacheItem.status();
}
private void putIntoCache(final Long id, final List<TotalTargetCountActionStatus> status,
@NotNull final Cache cache) {
private static void putIntoCache(final Long id, final List<TotalTargetCountActionStatus> status, @NotNull final Cache cache) {
cache.put(id, new CachedTotalTargetCountActionStatus(id, status));
}
private void putIntoCache(final Map<Long, List<TotalTargetCountActionStatus>> put, @NotNull final Cache cache) {
private static void putIntoCache(final Map<Long, List<TotalTargetCountActionStatus>> put, @NotNull final Cache cache) {
put.forEach((k, v) -> cache.put(k, new CachedTotalTargetCountActionStatus(k, v)));
}
private @NotNull Cache getRolloutStatusCache() {
private static @NotNull Cache getRolloutStatusCache() {
return Objects.requireNonNull(CACHE_MANAGER.getCache(CACHE_RO_NAME), "Cache '" + CACHE_RO_NAME + "' is null!");
}
private @NotNull Cache getGroupStatusCache() {
private static @NotNull Cache getGroupStatusCache() {
return Objects.requireNonNull(CACHE_MANAGER.getCache(CACHE_GR_NAME), "Cache '" + CACHE_RO_NAME + "' is null!");
}

View File

@@ -9,6 +9,11 @@
*/
package org.eclipse.hawkbit.event;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import java.util.HashMap;
import org.eclipse.hawkbit.repository.event.remote.AbstractRemoteEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.ActionCreatedEvent;
@@ -22,18 +27,12 @@ import org.eclipse.hawkbit.repository.event.remote.service.TargetDeletedServiceE
import org.eclipse.hawkbit.repository.event.remote.service.TargetUpdatedServiceEvent;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Target;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.MessageConverter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import java.util.HashMap;
abstract class AbstractEventMessageConverterTest {
protected final MessageConverter messageConverter;