diff --git a/hawkbit-core/src/main/java/org/eclipse/hawkbit/context/AccessContext.java b/hawkbit-core/src/main/java/org/eclipse/hawkbit/context/AccessContext.java index 63de3830d..9684cfc0f 100644 --- a/hawkbit-core/src/main/java/org/eclipse/hawkbit/context/AccessContext.java +++ b/hawkbit-core/src/main/java/org/eclipse/hawkbit/context/AccessContext.java @@ -12,6 +12,7 @@ package org.eclipse.hawkbit.context; import java.io.Serial; import java.io.Serializable; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -78,10 +79,7 @@ public class AccessContext { return null; } - // Sometimes 'system' need to override the auditor when do create/modify actions in context of a tenant and user. - // Though this could be made using runAsTenantAsUser sometimes (as in transaction) this override is needed - // after runAsTenantAsUser (because it seems that auditor is got in commit time). - // So this thread local variable provides option to override explicitly the auditor. + // Sometimes 'system' need to override the auditor when do create/modify actions in context of an actor private static final ThreadLocal ACTOR_OVERRIDE = new ThreadLocal<>(); // Return the current actor / auditor / principal name. It could be a user (person), technical user, device, etc. @@ -143,6 +141,34 @@ public class AccessContext { } } + /** + * Runs a given {@link Runnable} within a current authorities with set tenant in the context. + * + * @param tenant the tenant to be set in context. + * @param runnable the runnable to call within the tenant context + */ + public static void asTenant(final String tenant, final Runnable runnable) { + asTenant(tenant, () -> { + runnable.run(); + return null; + }); + } + + /** + * Runs a given {@link Supplier} within a current authorities with set tenant in the context. + * + * @param tenant the tenant to be set in context. + * @param supplier the supplier to call within the tenant context + * @return the return value of the {@link Supplier#get()} method. + */ + public static T asTenant(final String tenant, final Supplier supplier) { + final SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(new AuthenticationDelegate( + tenant, Optional.ofNullable(actor()).orElse(SYSTEM_ACTOR), + SecurityContextHolder.getContext().getAuthentication())); + return withSecurityContext(securityContext, supplier); + } + public static void asActor(final String actor, final Runnable runnable) { asActor(actor, () -> { runnable.run(); @@ -219,14 +245,12 @@ public class AccessContext { * @return the return value of the {@link Supplier#get()} method. */ public static T asSystemAsTenant(final String tenant, final Supplier supplier) { - final SecurityContext currentContext = SecurityContextHolder.getContext(); + log.debug("Entering system code execution"); try { - log.debug("Entering system code execution"); final SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); securityContext.setAuthentication(new SystemCodeAuthentication(tenant)); return withSecurityContext(securityContext, supplier); } finally { - SecurityContextHolder.setContext(currentContext); log.debug("Leaving system code execution"); } } @@ -446,4 +470,83 @@ public class AccessContext { throw new UnsupportedOperationException(); } } + + /** + * An {@link Authentication} implementation to delegate to an existing {@link Authentication} object except setting the details + * specifically for a specific tenant and user. + */ + private static final class AuthenticationDelegate implements Authentication { + + @Serial + private static final long serialVersionUID = 1L; + + private final Authentication delegate; + private final TenantAwareUser principal; + private final TenantAwareAuthenticationDetails tenantAwareAuthenticationDetails; + + private AuthenticationDelegate(final String tenant, final String username, final Authentication delegate) { + this.delegate = delegate; + principal = new TenantAwareUser(username, username, delegate != null ? delegate.getAuthorities() : Collections.emptyList(), tenant); + tenantAwareAuthenticationDetails = new TenantAwareAuthenticationDetails(tenant, false); + } + + @Override + public int hashCode() { + return delegate != null ? delegate.hashCode() : -1; + } + + @Override + public boolean equals(final Object another) { + if (another instanceof Authentication anotherAuthentication) { + return Objects.equals(delegate, anotherAuthentication) && + Objects.equals(principal, anotherAuthentication.getPrincipal()) && + Objects.equals(tenantAwareAuthenticationDetails, anotherAuthentication.getDetails()); + } else { + return false; + } + } + + @Override + public String toString() { + return delegate != null ? delegate.toString() : null; + } + + @Override + public String getName() { + return delegate != null ? delegate.getName() : null; + } + + @Override + public Collection getAuthorities() { + return principal.getAuthorities(); + } + + @Override + public Object getCredentials() { + return delegate != null ? delegate.getCredentials() : null; + } + + @Override + public Object getDetails() { + return tenantAwareAuthenticationDetails; + } + + @Override + public Object getPrincipal() { + return principal; + } + + @Override + public boolean isAuthenticated() { + return delegate == null || delegate.isAuthenticated(); + } + + @Override + public void setAuthenticated(final boolean isAuthenticated) { + if (delegate == null) { + return; + } + delegate.setAuthenticated(isAuthenticated); + } + } } \ No newline at end of file diff --git a/hawkbit-core/src/test/java/org/eclipse/hawkbit/context/SystemSecurityContextTest.java b/hawkbit-core/src/test/java/org/eclipse/hawkbit/context/AccessContextAsSystemTest.java similarity index 68% rename from hawkbit-core/src/test/java/org/eclipse/hawkbit/context/SystemSecurityContextTest.java rename to hawkbit-core/src/test/java/org/eclipse/hawkbit/context/AccessContextAsSystemTest.java index 47a3facf0..5ff048f1f 100644 --- a/hawkbit-core/src/test/java/org/eclipse/hawkbit/context/SystemSecurityContextTest.java +++ b/hawkbit-core/src/test/java/org/eclipse/hawkbit/context/AccessContextAsSystemTest.java @@ -9,11 +9,11 @@ */ package org.eclipse.hawkbit.context; +import static org.assertj.core.api.Assertions.assertThat; import static org.eclipse.hawkbit.context.AccessContext.asSystemAsTenant; import java.util.List; -import org.assertj.core.api.Assertions; import org.eclipse.hawkbit.auth.SpRole; import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails; import org.junit.jupiter.api.Test; @@ -23,32 +23,36 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; -class SystemSecurityContextTest { +class AccessContextAsSystemTest { @Test void test() { - final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("test", "pass", List.of(new SimpleGrantedAuthority("anonymous"))); + final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken( + "test", "pass", List.of(new SimpleGrantedAuthority("anonymous"))); auth.setDetails("string details"); test(auth); } @Test void testWithNullPrincipal() { - final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(null, "pass", List.of(new SimpleGrantedAuthority("anonymous"))); + final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken( + null, "pass", List.of(new SimpleGrantedAuthority("anonymous"))); auth.setDetails("string details"); test(auth); } @Test void testWithNullCredentials() { - final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("test", null, List.of(new SimpleGrantedAuthority("anonymous"))); + final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken( + "test", null, List.of(new SimpleGrantedAuthority("anonymous"))); auth.setDetails("string details"); test(auth); } @Test void testWitAllNull() { - final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(null, null, List.of(new SimpleGrantedAuthority("anonymous"))); + final UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken( + null, null, List.of(new SimpleGrantedAuthority("anonymous"))); auth.setDetails(null); test(auth); } @@ -59,10 +63,10 @@ class SystemSecurityContextTest { SecurityContextHolder.setContext(sc); asSystemAsTenant("tenant", () -> { final Authentication currentAuth = SecurityContextHolder.getContext().getAuthentication(); - Assertions.assertThat(currentAuth.getClass().getSimpleName()).isEqualTo("SystemCodeAuthentication"); - Assertions.assertThat(currentAuth.getCredentials()).isNull(); - Assertions.assertThat(currentAuth.getAuthorities()).isEqualTo(List.of(new SimpleGrantedAuthority(SpRole.SYSTEM_ROLE))); - Assertions.assertThat(currentAuth.getDetails()).isEqualTo(new TenantAwareAuthenticationDetails("tenant", false)); + assertThat(currentAuth.getClass().getSimpleName()).isEqualTo("SystemCodeAuthentication"); + assertThat(currentAuth.getCredentials()).isNull(); + assertThat(currentAuth.getAuthorities()).isEqualTo(List.of(new SimpleGrantedAuthority(SpRole.SYSTEM_ROLE))); + assertThat(currentAuth.getDetails()).isEqualTo(new TenantAwareAuthenticationDetails("tenant", false)); }); SecurityContextHolder.clearContext(); } diff --git a/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/Authenticator.java b/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/Authenticator.java index 86b1d7fff..753c7c4a6 100644 --- a/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/Authenticator.java +++ b/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/Authenticator.java @@ -9,7 +9,7 @@ */ package org.eclipse.hawkbit.security.controller; -import static org.eclipse.hawkbit.context.AccessContext.asSystemAsTenant; +import static org.eclipse.hawkbit.context.AccessContext.asTenant; import java.util.Collection; import java.util.List; @@ -47,9 +47,7 @@ public interface Authenticator { abstract class AbstractAuthenticator implements Authenticator { protected boolean isEnabled(final ControllerSecurityToken securityToken) { - return asSystemAsTenant( - securityToken.getTenant(), - () -> TenantConfigHelper.getAsSystem(getTenantConfigurationKey(), Boolean.class)); + return asTenant(securityToken.getTenant(), () -> TenantConfigHelper.getAsSystem(getTenantConfigurationKey(), Boolean.class)); } protected abstract String getTenantConfigurationKey(); diff --git a/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/GatewayTokenAuthenticator.java b/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/GatewayTokenAuthenticator.java index 7733f9e7f..528833d8e 100644 --- a/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/GatewayTokenAuthenticator.java +++ b/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/GatewayTokenAuthenticator.java @@ -9,7 +9,7 @@ */ package org.eclipse.hawkbit.security.controller; -import static org.eclipse.hawkbit.context.AccessContext.asSystemAsTenant; +import static org.eclipse.hawkbit.context.AccessContext.asTenant; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_ENABLED; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY; @@ -51,14 +51,10 @@ public class GatewayTokenAuthenticator extends Authenticator.AbstractAuthenticat final String presentedToken = authHeader.substring(OFFSET_GATEWAY_TOKEN); // validate if the presented token is the same as the gateway token - return presentedToken.equals(asSystemAsTenant( - controllerSecurityToken.getTenant(), - () -> { - log.trace("retrieving configuration value for configuration key {}", AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY); - return TenantConfigHelper.getAsSystem(AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY, String.class); - })) - ? authenticatedController(controllerSecurityToken.getTenant(), controllerSecurityToken.getControllerId()) - : null; + return presentedToken.equals(asTenant(controllerSecurityToken.getTenant(), () -> { + log.trace("retrieving configuration value for configuration key {}", AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY); + return TenantConfigHelper.getAsSystem(AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY, String.class); + })) ? authenticatedController(controllerSecurityToken.getTenant(), controllerSecurityToken.getControllerId()) : null; } @Override diff --git a/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/SecurityHeaderAuthenticator.java b/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/SecurityHeaderAuthenticator.java index 77fc63633..50d53628f 100644 --- a/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/SecurityHeaderAuthenticator.java +++ b/hawkbit-ddi/hawkbit-ddi-security/src/main/java/org/eclipse/hawkbit/security/controller/SecurityHeaderAuthenticator.java @@ -9,7 +9,7 @@ */ package org.eclipse.hawkbit.security.controller; -import static org.eclipse.hawkbit.context.AccessContext.asSystemAsTenant; +import static org.eclipse.hawkbit.context.AccessContext.asTenant; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_HEADER_AUTHORITY_NAME; import java.util.Arrays; @@ -73,7 +73,7 @@ public class SecurityHeaderAuthenticator extends Authenticator.AbstractAuthentic final String sslIssuerHashValue = getIssuerHashHeader( controllerSecurityToken, - asSystemAsTenant( + asTenant( controllerSecurityToken.getTenant(), () -> TenantConfigHelper.getAsSystem(AUTHENTICATION_HEADER_AUTHORITY_NAME, String.class))); if (sslIssuerHashValue == null) { diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java index 5cf4a54ff..5d48947ea 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/SystemManagement.java @@ -19,8 +19,6 @@ import org.eclipse.hawkbit.context.AccessContext; import org.eclipse.hawkbit.repository.model.DistributionSetType; import org.eclipse.hawkbit.repository.model.SoftwareModuleType; import org.eclipse.hawkbit.repository.model.TenantMetaData; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; import org.springframework.security.access.prepost.PreAuthorize; /** @@ -28,22 +26,15 @@ import org.springframework.security.access.prepost.PreAuthorize; */ public interface SystemManagement { - /** - * @param pageable for paging information - * @return list of all tenant names in the system. - */ - @PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN) - Page findTenants(@NotNull Pageable pageable); - /** * Runs consumer for each tenant as - * {@link AccessContext#asSystemAsTenant(String, java.util.concurrent.Callable)} + * {@link AccessContext#asSystemAsTenant(String, Runnable)} * silently (i.e. exceptions will be logged but operations will continue for further tenants). * * @param consumer to run as tenant */ @PreAuthorize(SpringEvalExpressions.IS_SYSTEM_CODE) - void forEachTenant(Consumer consumer); + void forEachTenantAsSystem(Consumer consumer); /** * @return {@link TenantMetaData} of {@link AccessContext#tenant()} diff --git a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/RolloutStatusCache.java b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/RolloutStatusCache.java index e6257a3d3..0072b4bd1 100644 --- a/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/RolloutStatusCache.java +++ b/hawkbit-repository/hawkbit-repository-core/src/main/java/org/eclipse/hawkbit/repository/RolloutStatusCache.java @@ -9,7 +9,7 @@ */ package org.eclipse.hawkbit.repository; -import static org.eclipse.hawkbit.context.AccessContext.asSystemAsTenant; +import static org.eclipse.hawkbit.context.AccessContext.asTenant; import java.util.Collections; import java.util.List; @@ -54,7 +54,6 @@ public class RolloutStatusCache { * Retrieves cached list of {@link TotalTargetCountActionStatus} of {@link Rollout}s. * * @param rolloutId to retrieve cache entries for - * @return map of cached entries */ public static List getRolloutStatus(final Long rolloutId) { return retrieveFromCache(rolloutId, getRolloutStatusCache()); @@ -74,7 +73,6 @@ public class RolloutStatusCache { * Retrieves cached list of {@link TotalTargetCountActionStatus} of {@link RolloutGroup}. * * @param groupId to retrieve cache entries for - * @return map of cached entries */ public static List getRolloutGroupStatus(final Long groupId) { return retrieveFromCache(groupId, getGroupStatusCache()); @@ -122,34 +120,29 @@ public class RolloutStatusCache { @EventListener(classes = AbstractActionEvent.class) public void invalidateCachedTotalTargetCountActionStatus(final AbstractActionEvent event) { if (event.getRolloutId() != null) { - final Cache cache = asSystemAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_RO_NAME)); - cache.evict(event.getRolloutId()); + asTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_RO_NAME)).evict(event.getRolloutId()); } if (event.getRolloutGroupId() != null) { - final Cache cache = asSystemAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_GR_NAME)); - cache.evict(event.getRolloutGroupId()); + asTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_GR_NAME)).evict(event.getRolloutGroupId()); } } @EventListener(classes = RolloutDeletedEvent.class) public void invalidateCachedTotalTargetCountOnRolloutDelete(final RolloutDeletedEvent event) { - final Cache cache = asSystemAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_RO_NAME)); - cache.evict(event.getEntityId()); + asTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_RO_NAME)).evict(event.getEntityId()); } @EventListener(classes = RolloutGroupDeletedEvent.class) public void invalidateCachedTotalTargetCountOnRolloutGroupDelete(final RolloutGroupDeletedEvent event) { - final Cache cache = asSystemAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_GR_NAME)); - cache.evict(event.getEntityId()); + asTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_GR_NAME)).evict(event.getEntityId()); } @EventListener(classes = RolloutStoppedEvent.class) public void invalidateCachedTotalTargetCountOnRolloutStopped(final RolloutStoppedEvent event) { - final Cache cache = asSystemAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_RO_NAME)); + final Cache cache = asTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_RO_NAME)); cache.evict(event.getRolloutId()); - event.getRolloutGroupIds().forEach( - groupId -> asSystemAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_GR_NAME)).evict(groupId)); + event.getRolloutGroupIds().forEach(groupId -> asTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_GR_NAME)).evict(groupId)); } private static @NotNull Map> retrieveFromCache(final List ids, @NotNull final Cache cache) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autocleanup/AutoCleanupScheduler.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autocleanup/AutoCleanupScheduler.java index c5b3e4aee..9ba6a48b0 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autocleanup/AutoCleanupScheduler.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/autocleanup/AutoCleanupScheduler.java @@ -65,7 +65,7 @@ public class AutoCleanupScheduler { */ @SuppressWarnings("squid:S3516") private Void executeAutoCleanup() { - systemManagement.forEachTenant(tenant -> cleanupTasks.forEach(task -> { + systemManagement.forEachTenantAsSystem(tenant -> cleanupTasks.forEach(task -> { final Lock lock = lockRegistry.obtain(AUTO_CLEANUP + SEP + task.getId() + SEP + tenant); if (!lock.tryLock()) { return; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/event/JpaEventEntityManager.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/event/JpaEventEntityManager.java index e23be40dd..b91683bc4 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/event/JpaEventEntityManager.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/event/JpaEventEntityManager.java @@ -9,7 +9,7 @@ */ package org.eclipse.hawkbit.repository.jpa.event; -import static org.eclipse.hawkbit.context.AccessContext.asSystemAsTenant; +import static org.eclipse.hawkbit.context.AccessContext.asTenant; import jakarta.persistence.EntityManager; @@ -36,6 +36,6 @@ public class JpaEventEntityManager implements EventEntityManager { @Override public E findEntity(final String tenant, final Long id, final Class entityType) { - return asSystemAsTenant(tenant, () -> entityManager.find(entityType, id)); + return asTenant(tenant, () -> entityManager.find(entityType, id)); } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java index 955c9f97a..2f7772512 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaControllerManagement.java @@ -11,7 +11,6 @@ package org.eclipse.hawkbit.repository.jpa.management; import static org.eclipse.hawkbit.context.AccessContext.asActor; import static org.eclipse.hawkbit.context.AccessContext.asSystem; -import static org.eclipse.hawkbit.context.AccessContext.asSystemAsTenant; import static org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor.afterCommit; import static org.eclipse.hawkbit.repository.model.Action.Status.DOWNLOADED; import static org.eclipse.hawkbit.repository.model.Action.Status.FINISHED; @@ -125,7 +124,6 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Transactional; -import org.springframework.transaction.support.TransactionCallback; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import org.springframework.validation.annotation.Validated; @@ -694,11 +692,10 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont } try { - events.stream().collect(Collectors.groupingBy(TargetPoll::getTenant)).forEach((tenant, polls) -> { - final TransactionCallback createTransaction = status -> updateLastTargetQueries(tenant, polls); - asSystemAsTenant( - tenant, () -> DeploymentHelper.runInNewTransaction(txManager, "flushUpdateQueue", createTransaction)); - }); + events.stream().collect(Collectors.groupingBy(TargetPoll::getTenant)) + .forEach((tenant, polls) -> DeploymentHelper.runInNewTransaction( + txManager, "flushUpdateQueue", + status -> updateLastTargetQueries(tenant, polls))); } catch (final RuntimeException ex) { log.error("Failed to persist UpdateQueue content.", ex); return; diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSystemManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSystemManagement.java index 57e7e4939..45f873375 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSystemManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaSystemManagement.java @@ -10,6 +10,7 @@ package org.eclipse.hawkbit.repository.jpa.management; import static org.eclipse.hawkbit.context.AccessContext.asSystemAsTenant; +import static org.eclipse.hawkbit.context.AccessContext.asTenant; import java.util.Set; import java.util.function.Consumer; @@ -136,20 +137,15 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst return currentTenantCacheKeyGenerator.currentTenantKeyGenerator(); } - @Override - public Page findTenants(final Pageable pageable) { - return tenantMetaDataRepository.findTenants(pageable); - } - @Override @Transactional(propagation = Propagation.NOT_SUPPORTED) // Exception squid:S2229 - calling findTenants without transaction is intended in this case @SuppressWarnings("squid:S2229") - public void forEachTenant(final Consumer consumer) { + public void forEachTenantAsSystem(final Consumer consumer) { Page tenants; Pageable query = PageRequest.of(0, MAX_TENANTS_QUERY); do { - tenants = findTenants(query); // with IS_SYSTEM_CODE so we could find all tenants + tenants = tenantMetaDataRepository.findTenants(query); // with IS_SYSTEM_CODE so we could find all tenants tenants.forEach(tenant -> asSystemAsTenant(tenant, () -> { try { consumer.accept(tenant); @@ -200,7 +196,7 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst } final String tenant = t.toUpperCase(); - asSystemAsTenant(tenant, () -> DeploymentHelper.runInNewTransaction(txManager, "deleteTenant", status -> { + asTenant(tenant, () -> DeploymentHelper.runInNewTransaction(txManager, "deleteTenant", status -> { tenantMetaDataRepository.deleteByTenantIgnoreCase(tenant); tenantConfigurationRepository.deleteByTenant(tenant); targetRepository.deleteByTenant(tenant); @@ -293,10 +289,9 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst * @return the initial created {@link TenantMetaData} */ private TenantMetaData createInitialTenantMetaData(final String tenant) { - return asSystemAsTenant( - tenant, () -> DeploymentHelper.runInNewTransaction(txManager, "initial-tenant-creation", status -> { - final DistributionSetType defaultDsType = createStandardSoftwareDataSetup(); - return tenantMetaDataRepository.save(new JpaTenantMetaData(defaultDsType, tenant)); - })); + return asSystemAsTenant(tenant, () -> DeploymentHelper.runInNewTransaction(txManager, "initial-tenant-creation", status -> { + final DistributionSetType defaultDsType = createStandardSoftwareDataSetup(); + return tenantMetaDataRepository.save(new JpaTenantMetaData(defaultDsType, tenant)); + })); } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/scheduler/AutoAssignScheduler.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/scheduler/AutoAssignScheduler.java index e8c0ca1af..57585086d 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/scheduler/AutoAssignScheduler.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/scheduler/AutoAssignScheduler.java @@ -68,7 +68,7 @@ public class AutoAssignScheduler { final long startNano = java.lang.System.nanoTime(); try { log.debug("Auto assign scheduled execution has acquired lock and started for each tenant."); - systemManagement.forEachTenant(tenant -> { + systemManagement.forEachTenantAsSystem(tenant -> { final long startNanoT = java.lang.System.nanoTime(); autoAssignExecutor.checkAllTargets(); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/scheduler/RolloutScheduler.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/scheduler/RolloutScheduler.java index b346e0715..35e5e41ab 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/scheduler/RolloutScheduler.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/scheduler/RolloutScheduler.java @@ -50,7 +50,7 @@ public class RolloutScheduler { } /** - * Scheduler method called by the spring-async mechanism. For all tenants, using {@link SystemManagement#forEachTenant}, + * Scheduler method called by the spring-async mechanism. For all tenants, using {@link SystemManagement#forEachTenantAsSystem}, * runs the {@link RolloutHandler#handleAll()} scoped to permission of access control context or unscoped (with {@link System}) if null */ @Scheduled(initialDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER) @@ -63,7 +63,7 @@ public class RolloutScheduler { // workaround eclipselink that is currently not possible to execute a query without multi-tenancy if MultiTenant // annotation is used. https://bugs.eclipse.org/bugs/show_bug.cgi?id=355458. So // iterate through all tenants and execute the rollout check for each tenant separately. - systemManagement.forEachTenant(tenant -> { + systemManagement.forEachTenantAsSystem(tenant -> { if (rolloutTaskExecutor == null) { handleAll(tenant); } else { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ConcurrentDistributionSetInvalidationTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ConcurrentDistributionSetInvalidationTest.java index 14189c252..3ad798684 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ConcurrentDistributionSetInvalidationTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/ConcurrentDistributionSetInvalidationTest.java @@ -17,7 +17,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import java.time.Duration; -import java.util.Collections; +import java.util.List; import java.util.concurrent.TimeUnit; import org.awaitility.Awaitility; @@ -63,8 +63,7 @@ class ConcurrentDistributionSetInvalidationTest extends AbstractJpaIntegrationTe final Rollout rollout = createRollout(distributionSet); final String tenant = AccessContext.tenant(); - // run in new Thread so that the invalidation can be executed in - // parallel + // run in new Thread so that the invalidation can be executed in parallel new Thread(() -> asSystemAsTenant(tenant, rolloutHandler::handleAll)).start(); // wait until at least one RolloutGroup is created, as this means that the thread has started and has acquired the lock @@ -74,7 +73,7 @@ class ConcurrentDistributionSetInvalidationTest extends AbstractJpaIntegrationTe .until(() -> asSystemAsTenant(tenant, () -> rolloutGroupManagement.findByRollout(rollout.getId(), PAGE).getSize() > 0)); final DistributionSetInvalidation distributionSetInvalidation = new DistributionSetInvalidation( - Collections.singletonList(distributionSet.getId()), ActionCancellationType.SOFT); + List.of(distributionSet.getId()), ActionCancellationType.SOFT); assertThatExceptionOfType(StopRolloutException.class) .as("Invalidation of distributionSet should throw an exception") .isThrownBy(() -> distributionSetInvalidationManagement.invalidateDistributionSet(distributionSetInvalidation)); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/SystemManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/SystemManagementTest.java index 168694e1a..954032fdb 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/SystemManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/management/SystemManagementTest.java @@ -12,6 +12,7 @@ package org.eclipse.hawkbit.repository.jpa.management; import static org.assertj.core.api.Assertions.assertThat; import java.io.ByteArrayInputStream; +import java.util.ArrayList; import java.util.List; import org.eclipse.hawkbit.auth.SpRole; @@ -37,11 +38,15 @@ class SystemManagementTest extends AbstractJpaIntegrationTest { */ @Test void findTenantsReturnsAllTenantsNotOnlyWhichLoggedIn() { - assertThat(systemManagement.findTenants(PAGE).getContent()).hasSize(1); - + assertThat(listTenants()).hasSize(1); createTestTenantsForSystemStatistics(2, 0, 0, 0); + assertThat(listTenants()).hasSize(3); + } - assertThat(systemManagement.findTenants(PAGE).getContent()).hasSize(3); + private List listTenants() { + final List tenants = new ArrayList<>(); + systemManagement.forEachTenantAsSystem(tenants::add); + return tenants; } private void createTestTenantsForSystemStatistics(final int tenants, final int artifactSize, final int targets, final int updates) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/tenancy/MultiTenancyEntityTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/tenancy/MultiTenancyEntityTest.java index 28ad17602..dd1e08fdd 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/tenancy/MultiTenancyEntityTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/tenancy/MultiTenancyEntityTest.java @@ -11,11 +11,14 @@ package org.eclipse.hawkbit.repository.jpa.tenancy; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +import static org.eclipse.hawkbit.context.AccessContext.asSystem; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.concurrent.Callable; +import org.eclipse.hawkbit.context.AccessContext; import org.eclipse.hawkbit.repository.exception.EntityNotFoundException; import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest; import org.eclipse.hawkbit.repository.model.DistributionSet; @@ -59,8 +62,7 @@ class MultiTenancyEntityTest extends AbstractJpaIntegrationTest { assertThat(findTargetsForTenant.getContent().get(0).getTenant().toUpperCase()).isEqualTo(tenant.toUpperCase()); final Page findTargetsForAnotherTenant = findTargetsForTenant(anotherTenant); assertThat(findTargetsForAnotherTenant).hasSize(1); - assertThat(findTargetsForAnotherTenant.getContent().get(0).getTenant().toUpperCase()) - .isEqualTo(anotherTenant.toUpperCase()); + assertThat(findTargetsForAnotherTenant.getContent().get(0).getTenant().toUpperCase()).isEqualTo(anotherTenant.toUpperCase()); } /** @@ -96,11 +98,9 @@ class MultiTenancyEntityTest extends AbstractJpaIntegrationTest { final String controllerAnotherTenant = "anotherController"; createTargetForTenant(controllerAnotherTenant, anotherTenant); - assertThat(systemManagement.findTenants(PAGE)).as("Expected number if tenants before deletion is").hasSize(3); - + assertThat(listTenants()).as("Expected number if tenants before deletion is").hasSize(3); systemManagement.deleteTenant(anotherTenant); - - assertThat(systemManagement.findTenants(PAGE)).as("Expected number if tenants after deletion is").hasSize(2); + assertThat(listTenants()).as("Expected number if tenants after deletion is").hasSize(2); } /** @@ -202,4 +202,10 @@ class MultiTenancyEntityTest extends AbstractJpaIntegrationTest { private Slice findDistributionSetForTenant(final String tenant) throws Exception { return runAsTenant(tenant, () -> distributionSetManagement.findAll(PAGE)); } + + private List listTenants() { + final List tenants = new ArrayList<>(); + asSystem(() -> systemManagement.forEachTenantAsSystem(tenants::add)); + return tenants; + } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/CleanupTestExecutionListener.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/CleanupTestExecutionListener.java index f1e998273..d5a3a80ec 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/CleanupTestExecutionListener.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/CleanupTestExecutionListener.java @@ -43,7 +43,7 @@ public class CleanupTestExecutionListener extends AbstractTestExecutionListener } private void clearTestRepository(final SystemManagement systemManagement) { - asSystem(() -> systemManagement.forEachTenant(tenant -> { + asSystem(() -> systemManagement.forEachTenantAsSystem(tenant -> { try { asSystem(() -> systemManagement.deleteTenant(tenant)); } catch (final Exception e) {