Refactor TenantAware - remove TenantRunner and replace with standard Runnable / Callable (#2755)

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-10-14 16:36:42 +03:00
committed by GitHub
parent 0a2f18fbad
commit 04cd9fb30d
14 changed files with 88 additions and 118 deletions

View File

@@ -9,6 +9,8 @@
*/ */
package org.eclipse.hawkbit.tenancy; package org.eclipse.hawkbit.tenancy;
import java.util.concurrent.Callable;
import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
@@ -30,45 +32,25 @@ public interface TenantAware {
String getCurrentUsername(); String getCurrentUsername();
/** /**
* Gives the possibility to run a certain code under a specific given {@code tenant}. Only the given {@link TenantRunner} is executed * Gives the possibility to run a certain code under a specific given {@code tenant}. Only the given {@link Callable} is executed
* under the specific tenant e.g. under control of an {@link ThreadLocal}. After the {@link TenantRunner} it must be ensured that the * under the specific tenant e.g. under control of an {@link ThreadLocal}. After the {@link Callable} it must be ensured that the
* original tenant before this invocation is reset. * original tenant before this invocation is reset.
* *
* @param tenant the tenant which the specific code should run * @param tenant the tenant which the specific code should run
* @param tenantRunner the runner which is implemented to run this specific code * @param callable the runner which is implemented to run this specific code under the given tenant
* under the given tenant * @return the return type of the {@link Callable}
* @return the return type of the {@link TenantRunner}
*/ */
<T> T runAsTenant(String tenant, TenantRunner<T> tenantRunner); <T> T runAsTenant(String tenant, Callable<T> callable);
/** /**
* Gives the possibility to run a certain code under a specific given {@code tenant} and {@code username}. * Gives the possibility to run a certain code under a specific given {@code tenant} and {@code username}.
* Only the given {@link TenantRunner} is executed under the specific tenant and user e.g. under control of an {@link ThreadLocal}. * Only the given {@link Runnable} is executed under the specific tenant and user e.g. under control of an {@link ThreadLocal}.
* After the {@link TenantRunner} it must be ensured that the original tenant before this invocation is reset. * After the {@link Runnable} it must be ensured that the original tenant before this invocation is reset.
* *
* @param tenant the tenant which the specific code should run with * @param tenant the tenant which the specific code should run with
* @param username the username which the specific code should run with * @param username the username which the specific code should run with
* @param tenantRunner the runner which is implemented to run this specific code under the given tenant
* @return the return type of the {@link TenantRunner}
*/ */
<T> T runAsTenantAsUser(String tenant, String username, TenantRunner<T> tenantRunner); void runAsTenantAsUser(String tenant, String username, Runnable runnable);
/**
* An {@link TenantRunner} interface which allows to run specific code under a given tenant by using the
* {@link TenantAware#runAsTenant(String, TenantRunner)}.
*
* @param <T> the return type of the runner
*/
@FunctionalInterface
interface TenantRunner<T> {
/**
* Called to run specific code and a given tenant.
*
* @return the return of the code block running under a certain tenant
*/
T run();
}
/** /**
* Resolves the tenant from the current context. * Resolves the tenant from the current context.

View File

@@ -12,6 +12,7 @@ package org.eclipse.hawkbit.security.controller;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.Callable;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.eclipse.hawkbit.im.authentication.SpRole; import org.eclipse.hawkbit.im.authentication.SpRole;
@@ -49,7 +50,7 @@ public interface Authenticator {
protected final TenantConfigurationManagement tenantConfigurationManagement; protected final TenantConfigurationManagement tenantConfigurationManagement;
protected final TenantAware tenantAware; protected final TenantAware tenantAware;
protected final SystemSecurityContext systemSecurityContext; protected final SystemSecurityContext systemSecurityContext;
private final TenantAware.TenantRunner<Boolean> isEnabledTenantRunner; private final Callable<Boolean> isEnabledGetter;
protected AbstractAuthenticator( protected AbstractAuthenticator(
final TenantConfigurationManagement tenantConfigurationManagement, final TenantConfigurationManagement tenantConfigurationManagement,
@@ -57,12 +58,12 @@ public interface Authenticator {
this.tenantConfigurationManagement = tenantConfigurationManagement; this.tenantConfigurationManagement = tenantConfigurationManagement;
this.tenantAware = tenantAware; this.tenantAware = tenantAware;
this.systemSecurityContext = systemSecurityContext; this.systemSecurityContext = systemSecurityContext;
isEnabledTenantRunner = () -> systemSecurityContext.runAsSystem( isEnabledGetter = () -> systemSecurityContext.runAsSystem(
() -> tenantConfigurationManagement.getConfigurationValue(getTenantConfigurationKey(), Boolean.class).getValue()); () -> tenantConfigurationManagement.getConfigurationValue(getTenantConfigurationKey(), Boolean.class).getValue());
} }
protected boolean isEnabled(final ControllerSecurityToken securityToken) { protected boolean isEnabled(final ControllerSecurityToken securityToken) {
return tenantAware.runAsTenant(securityToken.getTenant(), isEnabledTenantRunner); return tenantAware.runAsTenant(securityToken.getTenant(), isEnabledGetter);
} }
protected abstract String getTenantConfigurationKey(); protected abstract String getTenantConfigurationKey();

View File

@@ -9,6 +9,11 @@
*/ */
package org.eclipse.hawkbit.security.controller; package org.eclipse.hawkbit.security.controller;
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;
import java.util.concurrent.Callable;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.security.SystemSecurityContext;
@@ -30,19 +35,18 @@ public class GatewayTokenAuthenticator extends Authenticator.AbstractAuthenticat
public static final String GATEWAY_SECURITY_TOKEN_AUTH_SCHEME = "GatewayToken "; public static final String GATEWAY_SECURITY_TOKEN_AUTH_SCHEME = "GatewayToken ";
private static final int OFFSET_GATEWAY_TOKEN = GATEWAY_SECURITY_TOKEN_AUTH_SCHEME.length(); private static final int OFFSET_GATEWAY_TOKEN = GATEWAY_SECURITY_TOKEN_AUTH_SCHEME.length();
private final TenantAware.TenantRunner<String> gatewaySecurityTokenKeyConfigRunner; private final Callable<String> gatewaySecurityTokenKeyGetter;
public GatewayTokenAuthenticator( public GatewayTokenAuthenticator(
final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware, final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware,
final SystemSecurityContext systemSecurityContext) { final SystemSecurityContext systemSecurityContext) {
super(tenantConfigurationManagement, tenantAware, systemSecurityContext); super(tenantConfigurationManagement, tenantAware, systemSecurityContext);
gatewaySecurityTokenKeyConfigRunner = () -> { gatewaySecurityTokenKeyGetter = () -> {
log.trace("retrieving configuration value for configuration key {}", log.trace("retrieving configuration value for configuration key {}", AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY);
TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY);
return systemSecurityContext return systemSecurityContext
.runAsSystem(() -> tenantConfigurationManagement .runAsSystem(() -> tenantConfigurationManagement
.getConfigurationValue(TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY, String.class) .getConfigurationValue(AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY, String.class)
.getValue()); .getValue());
}; };
} }
@@ -67,7 +71,7 @@ public class GatewayTokenAuthenticator extends Authenticator.AbstractAuthenticat
final String presentedToken = authHeader.substring(OFFSET_GATEWAY_TOKEN); final String presentedToken = authHeader.substring(OFFSET_GATEWAY_TOKEN);
// validate if the presented token is the same as the gateway token // validate if the presented token is the same as the gateway token
return presentedToken.equals(tenantAware.runAsTenant(controllerSecurityToken.getTenant(), gatewaySecurityTokenKeyConfigRunner)) return presentedToken.equals(tenantAware.runAsTenant(controllerSecurityToken.getTenant(), gatewaySecurityTokenKeyGetter))
? authenticatedController(controllerSecurityToken.getTenant(), controllerSecurityToken.getControllerId()) : null; ? authenticatedController(controllerSecurityToken.getTenant(), controllerSecurityToken.getControllerId()) : null;
} }
@@ -78,6 +82,6 @@ public class GatewayTokenAuthenticator extends Authenticator.AbstractAuthenticat
@Override @Override
protected String getTenantConfigurationKey() { protected String getTenantConfigurationKey() {
return TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_ENABLED; return AUTHENTICATION_GATEWAY_SECURITY_TOKEN_ENABLED;
} }
} }

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.security.controller;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
@@ -47,7 +48,7 @@ public class SecurityHeaderAuthenticator extends Authenticator.AbstractAuthentic
// header exists multiple times in the request for all trusted chains. // header exists multiple times in the request for all trusted chains.
private final String sslIssuerHashBasicHeader; private final String sslIssuerHashBasicHeader;
private final TenantAware.TenantRunner<String> sslIssuerNameConfigTenantRunner; private final Callable<String> sslIssuerNameConfigGetter;
public SecurityHeaderAuthenticator( public SecurityHeaderAuthenticator(
final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware, final TenantConfigurationManagement tenantConfigurationManagement, final TenantAware tenantAware,
@@ -56,7 +57,7 @@ public class SecurityHeaderAuthenticator extends Authenticator.AbstractAuthentic
super(tenantConfigurationManagement, tenantAware, systemSecurityContext); super(tenantConfigurationManagement, tenantAware, systemSecurityContext);
this.caCommonNameHeader = caCommonNameHeader; this.caCommonNameHeader = caCommonNameHeader;
this.sslIssuerHashBasicHeader = caAuthorityNameHeader; this.sslIssuerHashBasicHeader = caAuthorityNameHeader;
sslIssuerNameConfigTenantRunner = () -> systemSecurityContext.runAsSystem( sslIssuerNameConfigGetter = () -> systemSecurityContext.runAsSystem(
() -> tenantConfigurationManagement.getConfigurationValue( () -> tenantConfigurationManagement.getConfigurationValue(
TenantConfigurationKey.AUTHENTICATION_HEADER_AUTHORITY_NAME, String.class).getValue()); TenantConfigurationKey.AUTHENTICATION_HEADER_AUTHORITY_NAME, String.class).getValue());
} }
@@ -81,7 +82,7 @@ public class SecurityHeaderAuthenticator extends Authenticator.AbstractAuthentic
final String sslIssuerHashValue = getIssuerHashHeader( final String sslIssuerHashValue = getIssuerHashHeader(
controllerSecurityToken, controllerSecurityToken,
tenantAware.runAsTenant(controllerSecurityToken.getTenant(), sslIssuerNameConfigTenantRunner)); tenantAware.runAsTenant(controllerSecurityToken.getTenant(), sslIssuerNameConfigGetter));
if (sslIssuerHashValue == null) { if (sslIssuerHashValue == null) {
log.debug("The request contains the 'common name' header but trusted hash is not found"); log.debug("The request contains the 'common name' header but trusted hash is not found");
return null; return null;

View File

@@ -47,7 +47,7 @@ public interface SystemManagement {
/** /**
* Runs consumer for each tenant as * Runs consumer for each tenant as
* {@link TenantAware#runAsTenant(String, org.eclipse.hawkbit.tenancy.TenantAware.TenantRunner)} * {@link TenantAware#runAsTenant(String, java.util.concurrent.Callable)}
* silently (i.e. exceptions will be logged but operations will continue for further tenants). * silently (i.e. exceptions will be logged but operations will continue for further tenants).
* *
* @param consumer to run as tenant * @param consumer to run as tenant

View File

@@ -15,46 +15,37 @@ import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy; import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.model.Rollout; import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.util.ObjectUtils;
/** /**
* Default implementation of {@link RolloutApprovalStrategy}. Decides whether * Default implementation of {@link RolloutApprovalStrategy}. Decides whether approval is needed based on configuration of the tenant as well
* approval is needed based on configuration of the tenant as well as the roles * as the roles of the user who created the Rollout. Provides a no-operation implementation of
* of the user who created the Rollout. Provides a no-operation implementation * {@link RolloutApprovalStrategy#onApprovalRequired(Rollout)}.
* of {@link RolloutApprovalStrategy#onApprovalRequired(Rollout)}.
*/ */
public class DefaultRolloutApprovalStrategy implements RolloutApprovalStrategy { public class DefaultRolloutApprovalStrategy implements RolloutApprovalStrategy {
private final UserAuthoritiesResolver userAuthoritiesResolver;
private final TenantConfigurationManagement tenantConfigurationManagement; private final TenantConfigurationManagement tenantConfigurationManagement;
private final SystemSecurityContext systemSecurityContext; private final SystemSecurityContext systemSecurityContext;
DefaultRolloutApprovalStrategy( DefaultRolloutApprovalStrategy(
final UserAuthoritiesResolver userAuthoritiesResolver,
final TenantConfigurationManagement tenantConfigurationManagement, final TenantConfigurationManagement tenantConfigurationManagement,
final SystemSecurityContext systemSecurityContext) { final SystemSecurityContext systemSecurityContext) {
this.userAuthoritiesResolver = userAuthoritiesResolver;
this.tenantConfigurationManagement = tenantConfigurationManagement; this.tenantConfigurationManagement = tenantConfigurationManagement;
this.systemSecurityContext = systemSecurityContext; this.systemSecurityContext = systemSecurityContext;
} }
/** /**
* Returns true, if rollout approval is enabled and rollout creator doesn't * Returns true, if rollout approval is enabled and rollout creator doesn't have approval role. It have to be called in the user context
* have approval role.
*/ */
@Override @Override
public boolean isApprovalNeeded(final Rollout rollout) { public boolean isApprovalNeeded(final Rollout rollout) {
return isApprovalEnabled() && hasNoApproveRolloutPermission(getActorAuthorities(rollout)); return isApprovalEnabled() && hasNoApproveRolloutPermission(
getCurrentAuthentication().getAuthorities().stream().map(GrantedAuthority::getAuthority).toList());
} }
/*** /***
@@ -85,19 +76,4 @@ public class DefaultRolloutApprovalStrategy implements RolloutApprovalStrategy {
return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement
.getConfigurationValue(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, Boolean.class).getValue()); .getConfigurationValue(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, Boolean.class).getValue());
} }
private Collection<String> getActorAuthorities(final Rollout rollout) {
// rollout state transition from CREATING to CREATED is managed by
// scheduler under SYSTEM user context, thus we get the
// user based on the properties of initially created rollout entity
if (RolloutStatus.CREATING == rollout.getStatus()) {
final String actor = rollout.getLastModifiedBy() != null ? rollout.getLastModifiedBy() : rollout.getCreatedBy();
if (!ObjectUtils.isEmpty(actor)) {
return systemSecurityContext.runAsSystem(() -> userAuthoritiesResolver.getUserAuthorities(rollout.getTenant(), actor));
}
}
return ((User) getCurrentAuthentication().getPrincipal()).getAuthorities().stream()
.map(GrantedAuthority::getAuthority).toList();
}
} }

View File

@@ -106,7 +106,6 @@ import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
import org.eclipse.hawkbit.security.SecurityTokenGenerator; import org.eclipse.hawkbit.security.SecurityTokenGenerator;
import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver;
import org.springframework.beans.BeansException; import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
@@ -397,10 +396,9 @@ public class JpaRepositoryConfiguration {
*/ */
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
RolloutApprovalStrategy rolloutApprovalStrategy(final UserAuthoritiesResolver userAuthoritiesResolver, RolloutApprovalStrategy rolloutApprovalStrategy(
final TenantConfigurationManagement tenantConfigurationManagement, final TenantConfigurationManagement tenantConfigurationManagement, final SystemSecurityContext systemSecurityContext) {
final SystemSecurityContext systemSecurityContext) { return new DefaultRolloutApprovalStrategy(tenantConfigurationManagement, systemSecurityContext);
return new DefaultRolloutApprovalStrategy(userAuthoritiesResolver, tenantConfigurationManagement, systemSecurityContext);
} }
/** /**

View File

@@ -155,10 +155,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
context -> // has stored context - executes it with it context -> // has stored context - executes it with it
contextAware.runInContext(context, () -> execute0(rollout)), contextAware.runInContext(context, () -> execute0(rollout)),
() -> // has no stored context - executes it in the tenant & user scope () -> // has no stored context - executes it in the tenant & user scope
contextAware.runAsTenantAsUser(contextAware.getCurrentTenant(), rollout.getCreatedBy(), () -> { contextAware.runAsTenantAsUser(contextAware.getCurrentTenant(), rollout.getCreatedBy(), () -> execute0(rollout)));
execute0(rollout);
return null;
}));
} }
private void execute0(final Rollout rollout) { private void execute0(final Rollout rollout) {

View File

@@ -142,10 +142,7 @@ public class AutoAssignChecker implements AutoAssignExecutor {
() -> // has no stored context - executes it in the tenant & user scope () -> // has no stored context - executes it in the tenant & user scope
contextAware.runAsTenantAsUser( contextAware.runAsTenantAsUser(
contextAware.getCurrentTenant(), contextAware.getCurrentTenant(),
getAutoAssignmentInitiatedBy(filterQuery), () -> { getAutoAssignmentInitiatedBy(filterQuery), () -> consumer.accept(filterQuery))
consumer.accept(filterQuery);
return null;
})
); );
} catch (final RuntimeException ex) { } catch (final RuntimeException ex) {
if (log.isDebugEnabled()) { if (log.isDebugEnabled()) {

View File

@@ -9,6 +9,7 @@
*/ */
package org.eclipse.hawkbit.repository.jpa.management; package org.eclipse.hawkbit.repository.jpa.management;
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_GATEWAY_SECURITY_TOKEN;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.BATCH_ASSIGNMENTS_ENABLED; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.BATCH_ASSIGNMENTS_ENABLED;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED; import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED;
@@ -28,7 +29,6 @@ import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.repository.TargetFields; import org.eclipse.hawkbit.repository.TargetFields;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.event.remote.TenantConfigurationDeletedEvent; import org.eclipse.hawkbit.repository.event.remote.TenantConfigurationDeletedEvent;
@@ -248,10 +248,9 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
private void checkAccess(final String configurationKeyName) { private void checkAccess(final String configurationKeyName) {
if (AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY.equalsIgnoreCase(configurationKeyName)) { if (AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY.equalsIgnoreCase(configurationKeyName)) {
final SystemSecurityContext systemSecurityContext = SystemSecurityContextHolder.getInstance().getSystemSecurityContext(); final SystemSecurityContext systemSecurityContext = SystemSecurityContextHolder.getInstance().getSystemSecurityContext();
if (!systemSecurityContext.isCurrentThreadSystemCode() && if (!SystemSecurityContext.isCurrentThreadSystemCode() && !systemSecurityContext.hasPermission(READ_GATEWAY_SECURITY_TOKEN)) {
!systemSecurityContext.hasPermission(SpPermission.READ_GATEWAY_SECURITY_TOKEN)) {
throw new InsufficientPermissionException( throw new InsufficientPermissionException(
"Can't read gateway security token! " + SpPermission.READ_GATEWAY_SECURITY_TOKEN + " is required!"); "Can't read gateway security token! " + READ_GATEWAY_SECURITY_TOKEN + " is required!");
} }
} }
} }

View File

@@ -216,7 +216,7 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
@Override @Override
public String getSecurityToken() { public String getSecurityToken() {
final SystemSecurityContext systemSecurityContext = SystemSecurityContextHolder.getInstance().getSystemSecurityContext(); final SystemSecurityContext systemSecurityContext = SystemSecurityContextHolder.getInstance().getSystemSecurityContext();
if (systemSecurityContext.isCurrentThreadSystemCode() || systemSecurityContext.hasPermission(SpPermission.READ_TARGET_SECURITY_TOKEN)) { if (SystemSecurityContext.isCurrentThreadSystemCode() || systemSecurityContext.hasPermission(SpPermission.READ_TARGET_SECURITY_TOKEN)) {
return securityToken; return securityToken;
} }
return null; return null;

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.jpa.autoassign;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@@ -27,8 +28,8 @@ import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.TargetManagement; import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.model.DeploymentRequest; import org.eclipse.hawkbit.repository.model.DeploymentRequest;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@@ -47,9 +48,9 @@ import org.springframework.transaction.PlatformTransactionManager;
class AutoAssignCheckerTest { class AutoAssignCheckerTest {
@Mock @Mock
private TargetFilterQueryManagement targetFilterQueryManagement; private TargetFilterQueryManagement<? extends TargetFilterQuery> targetFilterQueryManagement;
@Mock @Mock
private TargetManagement targetManagement; private TargetManagement<? extends Target> targetManagement;
@Mock @Mock
private DeploymentManagement deploymentManagement; private DeploymentManagement deploymentManagement;
@Mock @Mock
@@ -57,12 +58,12 @@ class AutoAssignCheckerTest {
@Mock @Mock
private ContextAware contextAware; private ContextAware contextAware;
private AutoAssignChecker sut; private AutoAssignChecker autoAssignChecker;
@BeforeEach @BeforeEach
void before() { void before() {
sut = new AutoAssignChecker(targetFilterQueryManagement, targetManagement, deploymentManagement, autoAssignChecker = new AutoAssignChecker(
transactionManager, contextAware); targetFilterQueryManagement, targetManagement, deploymentManagement, transactionManager, contextAware);
} }
/** /**
@@ -75,18 +76,15 @@ class AutoAssignCheckerTest {
final long ds = getRandomLong(); final long ds = getRandomLong();
final TargetFilterQuery matching = mockFilterQuery(ds); final TargetFilterQuery matching = mockFilterQuery(ds);
final TargetFilterQuery notMatching = mockFilterQuery(ds); final TargetFilterQuery notMatching = mockFilterQuery(ds);
when(targetFilterQueryManagement.findWithAutoAssignDS(any())) when(targetFilterQueryManagement.findWithAutoAssignDS(any())).thenReturn(new SliceImpl<>(Arrays.asList(notMatching, matching)));
.thenReturn(new SliceImpl<>(Arrays.asList(notMatching, matching))); when(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(target, ds, matching.getQuery())).thenReturn(true);
when(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(target, ds, matching.getQuery()))
.thenReturn(true);
when(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(target, ds, notMatching.getQuery())) when(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(target, ds, notMatching.getQuery()))
.thenReturn(false); .thenReturn(false);
sut.checkSingleTarget(target); autoAssignChecker.checkSingleTarget(target);
verify(deploymentManagement).assignDistributionSets(eq(matching.getAutoAssignInitiatedBy()), verify(deploymentManagement).assignDistributionSets(
Mockito.argThat(deployReqMatcher(target, ds)), any()); eq(matching.getAutoAssignInitiatedBy()), Mockito.argThat(deployReqMatcher(target, ds)), any());
Mockito.verifyNoMoreInteractions(deploymentManagement); Mockito.verifyNoMoreInteractions(deploymentManagement);
} }
@@ -118,8 +116,9 @@ class AutoAssignCheckerTest {
private void mockRunningAsNonSystem() { private void mockRunningAsNonSystem() {
when(contextAware.getCurrentTenant()).thenReturn(getRandomString()); when(contextAware.getCurrentTenant()).thenReturn(getRandomString());
when(contextAware.runAsTenantAsUser(any(String.class), any(String.class), any(TenantAware.TenantRunner.class))) doAnswer(i -> {
.thenAnswer(i -> ((TenantAware.TenantRunner) i.getArgument(2)).run()); ((Runnable) i.getArgument(2)).run();
return null;
}).when(contextAware).runAsTenantAsUser(any(String.class), any(String.class), any(Runnable.class));
} }
}
}

View File

@@ -15,6 +15,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
@@ -106,12 +107,21 @@ public class SecurityContextTenantAware implements ContextAware {
} }
@Override @Override
public <T> T runAsTenant(final String tenant, final TenantRunner<T> tenantRunner) { @SuppressWarnings("java:S112") // java:S112 - it is generic class so a generic exception is fine
return runInContext(buildUserSecurityContext(tenant, SYSTEM_USER, SYSTEM_AUTHORITIES), tenantRunner::run); public <T> T runAsTenant(final String tenant, final Callable<T> callable) {
return runInContext(buildUserSecurityContext(tenant, SYSTEM_USER, SYSTEM_AUTHORITIES), () -> {
try {
return callable.call();
} catch (final RuntimeException e) {
throw e;
} catch (final Exception e) {
throw new RuntimeException(e);
}
});
} }
@Override @Override
public <T> T runAsTenantAsUser(final String tenant, final String username, final TenantRunner<T> tenantRunner) { public void runAsTenantAsUser(final String tenant, final String username, final Runnable runnable) {
Objects.requireNonNull(tenant); Objects.requireNonNull(tenant);
Objects.requireNonNull(username); Objects.requireNonNull(username);
@@ -119,7 +129,10 @@ public class SecurityContextTenantAware implements ContextAware {
() -> authoritiesResolver.getUserAuthorities(tenant, username).stream() () -> authoritiesResolver.getUserAuthorities(tenant, username).stream()
.map(SimpleGrantedAuthority::new) .map(SimpleGrantedAuthority::new)
.toList()); .toList());
return runInContext(buildUserSecurityContext(tenant, username, authorities), tenantRunner::run); runInContext(buildUserSecurityContext(tenant, username, authorities), () -> {
runnable.run();
return null;
});
} }
@Override @Override
@@ -151,11 +164,11 @@ public class SecurityContextTenantAware implements ContextAware {
} }
} }
private static <T> T runAsSystem(final TenantRunner<T> tenantRunner) { private static <T> T runAsSystem(final Callable<T> callable) {
final SecurityContext currentContext = SecurityContextHolder.getContext(); final SecurityContext currentContext = SecurityContextHolder.getContext();
SystemSecurityContext.setSystemContext(currentContext); SystemSecurityContext.setSystemContext(currentContext);
try { try {
return MdcHandler.getInstance().callWithAuthRE(tenantRunner::run); return MdcHandler.getInstance().callWithAuthRE(callable);
} finally { } finally {
SecurityContextHolder.setContext(currentContext); SecurityContextHolder.setContext(currentContext);
} }

View File

@@ -15,7 +15,9 @@ import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable;
import lombok.SneakyThrows;
import org.assertj.core.api.Assertions; import org.assertj.core.api.Assertions;
import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.TenantAware;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -39,14 +41,15 @@ class SystemCodeAuthenticationTest {
return "user"; return "user";
} }
@SneakyThrows
@Override @Override
public <T> T runAsTenant(final String tenant, final TenantRunner<T> tenantRunner) { public <T> T runAsTenant(final String tenant, final Callable<T> callable) {
return tenantRunner.run(); return callable.call();
} }
@Override @Override
public <T> T runAsTenantAsUser(final String tenant, final String username, final TenantRunner<T> tenantRunner) { public void runAsTenantAsUser(final String tenant, final String username, final Runnable runnable) {
return tenantRunner.run(); runnable.run();
} }
}); });