diff --git a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java
index 85b5b80c6..53b462033 100644
--- a/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java
+++ b/hawkbit-autoconfigure/src/main/java/org/eclipse/hawkbit/autoconfigure/security/SecurityAutoConfiguration.java
@@ -14,17 +14,20 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
+import org.eclipse.hawkbit.ContextAware;
import org.eclipse.hawkbit.autoconfigure.security.MultiUserProperties.User;
import org.eclipse.hawkbit.im.authentication.PermissionService;
import org.eclipse.hawkbit.security.DdiSecurityProperties;
import org.eclipse.hawkbit.security.InMemoryUserAuthoritiesResolver;
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.UserAuthoritiesResolver;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
@@ -49,20 +52,22 @@ import org.springframework.util.CollectionUtils;
public class SecurityAutoConfiguration {
/**
- * Creates a {@link TenantAware} bean based on the given
- * {@link UserAuthoritiesResolver}.
- *
+ * Creates a {@link ContextAware} (hence {@link TenantAware}) bean based on the given
+ * {@link UserAuthoritiesResolver} and {@link SecurityContextSerializer}.
+ *
* @param authoritiesResolver
* The user authorities/roles resolver
- *
- * @return the {@link TenantAware} singleton bean which holds the current
- * {@link TenantAware} service and make it accessible in beans which
- * cannot access the service directly, e.g. JPA entities.
+ * @param securityContextSerializer
+ * The security context serializer.
+ *
+ * @return the {@link ContextAware} singleton bean.
*/
@Bean
@ConditionalOnMissingBean
- public TenantAware tenantAware(final UserAuthoritiesResolver authoritiesResolver) {
- return new SecurityContextTenantAware(authoritiesResolver);
+ public ContextAware contextAware(
+ final UserAuthoritiesResolver authoritiesResolver,
+ @Autowired(required = false) final SecurityContextSerializer securityContextSerializer) {
+ return new SecurityContextTenantAware(authoritiesResolver, securityContextSerializer);
}
/**
diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/ContextAware.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/ContextAware.java
new file mode 100644
index 000000000..9fb9d3303
--- /dev/null
+++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/ContextAware.java
@@ -0,0 +1,62 @@
+/**
+ * Copyright (c) 2023 Bosch.IO GmbH and others
+ *
+ * This program and the accompanying materials are made
+ * available under the terms of the Eclipse Public License 2.0
+ * which is available at https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.eclipse.hawkbit;
+
+import org.eclipse.hawkbit.tenancy.TenantAware;
+
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Function;
+
+/**
+ * {@link ContextAware} provides means for getting the current context (via {@link #getCurrentContext()}) and then
+ * to execute a {@link Runnable} or a {@link Function} in the same context using {@link #runInContext(String, Runnable)}
+ * or {@link #runInContext(String, Function, Object)}.
+ *
+ * This is useful for scheduled background operations like rollouts and auto assignments where they shall
+ * be processed in the scope of the creator.
+ */
+public interface ContextAware extends TenantAware {
+
+ /**
+ * Return the current context encoded as a {@link String}. Depending on the implementation it could,
+ * for instance, be a serialized context or a reference to such.
+ *
+ * @return could be empty if there is nothing to serialize or context aware is not supported.
+ */
+ Optional getCurrentContext();
+
+ /**
+ * Wrap a specific execution in a known and pre-serialized context.
+ *
+ * @param the type of the input to the function
+ * @param the type of the result of the function
+ *
+ * @param serializedContext created by {@link #getCurrentContext()}. Must be non-null.
+ * @param function function to call in the reconstructed context. Must be non-null.
+ * @param t the argument that will be passed to the function
+ * @return the function result
+ */
+ R runInContext(String serializedContext, Function function, T t);
+
+ /**
+ * Wrap a specific execution in a known and pre-serialized context.
+ *
+ * @param serializedContext created by {@link #getCurrentContext()}. Must be non-null.
+ * @param runnable runnable to call in the reconstructed context. Must be non-null.
+ */
+ default void runInContext(String serializedContext, Runnable runnable) {
+ Objects.requireNonNull(runnable);
+ runInContext(serializedContext, v -> {
+ runnable.run();
+ return null;
+ }, null);
+ }
+}
\ No newline at end of file
diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/TenantAware.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/TenantAware.java
index e12cd96e5..ca4246266 100644
--- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/TenantAware.java
+++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/tenancy/TenantAware.java
@@ -40,8 +40,6 @@ public interface TenantAware {
* the runner which is implemented to run this specific code
* under the given tenant
* @return the return type of the {@link TenantRunner}
- * @throws any
- * kind of {@link RuntimeException}
*/
T runAsTenant(String tenant, TenantRunner tenantRunner);
@@ -60,8 +58,6 @@ public interface TenantAware {
* the runner which is implemented to run this specific code
* under the given tenant
* @return the return type of the {@link TenantRunner}
- * @throws any
- * kind of {@link RuntimeException}
*/
T runAsTenantAsUser(String tenant, String username, TenantRunner tenantRunner);
diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java
index a459141d6..55a6180c7 100644
--- a/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java
+++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/main/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherService.java
@@ -190,7 +190,7 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
private List getTargetsWithoutPendingCancellations(final Set controllerIds) {
return partitionedParallelExecution(controllerIds, partition -> {
return targetManagement.getByControllerID(partition).stream().filter(target -> {
- if (hasPendingCancellations(target.getControllerId())) {
+ if (hasPendingCancellations(target.getId())) {
LOG.debug("Target {} has pending cancellations. Will not send update message to it.",
target.getControllerId());
return false;
@@ -469,8 +469,8 @@ public class AmqpMessageDispatcherService extends BaseAmqpService {
return serviceMatcher == null || serviceMatcher.isFromSelf(event);
}
- private boolean hasPendingCancellations(final String controllerId) {
- return deploymentManagement.hasPendingCancellations(controllerId);
+ private boolean hasPendingCancellations(final Long targetId) {
+ return deploymentManagement.hasPendingCancellations(targetId);
}
protected void sendCancelMessageToTarget(final String tenant, final String controllerId, final Long actionId,
diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java
index 358cc24a4..da31d2215 100644
--- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java
+++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpControllerAuthenticationTest.java
@@ -44,6 +44,7 @@ import org.eclipse.hawkbit.security.DdiSecurityProperties.Authentication.Anonymo
import org.eclipse.hawkbit.security.DdiSecurityProperties.Rp;
import org.eclipse.hawkbit.security.DmfTenantSecurityToken;
import org.eclipse.hawkbit.security.DmfTenantSecurityToken.FileResource;
+import org.eclipse.hawkbit.security.SecurityContextSerializer;
import org.eclipse.hawkbit.security.SecurityContextTenantAware;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver;
@@ -113,6 +114,9 @@ public class AmqpControllerAuthenticationTest {
@Mock
private UserAuthoritiesResolver authoritiesResolver;
+ @Mock
+ private SecurityContextSerializer securityContextSerializer;
+
@Mock
private RabbitTemplate rabbitTemplate;
@@ -147,7 +151,7 @@ public class AmqpControllerAuthenticationTest {
when(tenantConfigurationManagementMock.getConfigurationValue(any(), eq(Boolean.class)))
.thenReturn(CONFIG_VALUE_FALSE);
- final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(authoritiesResolver);
+ final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(authoritiesResolver, securityContextSerializer);
final SystemSecurityContext systemSecurityContext = new SystemSecurityContext(tenantAware);
authenticationManager = new AmqpControllerAuthentication(systemManagement, controllerManagement,
diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java
index fbbe9e974..e59888d4a 100644
--- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java
+++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageDispatcherServiceTest.java
@@ -149,9 +149,10 @@ class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
assertThat(softwareModule.getArtifacts().isEmpty()).as("Artifact list for softwaremodule should be empty")
.isTrue();
- assertThat(softwareModule.getMetadata()).containsExactly(
- new DmfMetadata(TestdataFactory.VISIBLE_SM_MD_KEY, TestdataFactory.VISIBLE_SM_MD_VALUE));
-
+ assertThat(softwareModule.getMetadata()).allSatisfy(metadata -> {
+ assertThat(metadata.getKey()).isEqualTo(TestdataFactory.VISIBLE_SM_MD_KEY);
+ assertThat(metadata.getValue()).isEqualTo(TestdataFactory.VISIBLE_SM_MD_VALUE);
+ });
for (final SoftwareModule softwareModule2 : action.getDistributionSet().getModules()) {
if (!softwareModule.getModuleId().equals(softwareModule2.getId())) {
continue;
diff --git a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java
index 64e789533..ae86ace38 100644
--- a/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java
+++ b/hawkbit-dmf/hawkbit-dmf-amqp/src/test/java/org/eclipse/hawkbit/amqp/AmqpMessageHandlerServiceTest.java
@@ -63,6 +63,7 @@ import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
import org.eclipse.hawkbit.security.DmfTenantSecurityToken;
import org.eclipse.hawkbit.security.DmfTenantSecurityToken.FileResource;
+import org.eclipse.hawkbit.security.SecurityContextSerializer;
import org.eclipse.hawkbit.security.SecurityContextTenantAware;
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
import org.eclipse.hawkbit.security.SystemSecurityContext;
@@ -148,6 +149,9 @@ public class AmqpMessageHandlerServiceTest {
@Mock
private UserAuthoritiesResolver authoritiesResolver;
+ @Mock
+ private SecurityContextSerializer securityContextSerializer;
+
@Captor
private ArgumentCaptor