Execute rollouts and auto assignments in the correct user context (#1100)

* Execute rollouts and auto assignments in correct user context

Signed-off-by: Stefan Behl <stefan.behl@bosch.io>

* Fix PR review findings

Signed-off-by: Stefan Behl <stefan.behl@bosch.io>

* Cleanup usage of lenient

Signed-off-by: Stefan Behl <stefan.behl@bosch.io>
This commit is contained in:
Stefan Behl
2021-04-15 12:23:14 +02:00
committed by GitHub
parent eaf6be8c94
commit cf67467fb5
14 changed files with 354 additions and 90 deletions

View File

@@ -8,16 +8,25 @@
*/ */
package org.eclipse.hawkbit.autoconfigure.security; package org.eclipse.hawkbit.autoconfigure.security;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.autoconfigure.security.MultiUserProperties.User;
import org.eclipse.hawkbit.im.authentication.PermissionService; import org.eclipse.hawkbit.im.authentication.PermissionService;
import org.eclipse.hawkbit.security.DdiSecurityProperties; import org.eclipse.hawkbit.security.DdiSecurityProperties;
import org.eclipse.hawkbit.security.InMemoryUserAuthoritiesResolver;
import org.eclipse.hawkbit.security.HawkbitSecurityProperties; import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
import org.eclipse.hawkbit.security.SecurityContextTenantAware; import org.eclipse.hawkbit.security.SecurityContextTenantAware;
import org.eclipse.hawkbit.security.SecurityTokenGenerator; import org.eclipse.hawkbit.security.SecurityTokenGenerator;
import org.eclipse.hawkbit.security.SpringSecurityAuditorAware; import org.eclipse.hawkbit.security.SpringSecurityAuditorAware;
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.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@@ -28,23 +37,57 @@ import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
import org.springframework.util.CollectionUtils;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} for security. * {@link EnableAutoConfiguration Auto-configuration} for security.
*/ */
@Configuration @Configuration
@EnableConfigurationProperties({ DdiSecurityProperties.class, HawkbitSecurityProperties.class }) @EnableConfigurationProperties({ SecurityProperties.class, DdiSecurityProperties.class, HawkbitSecurityProperties.class,
MultiUserProperties.class })
public class SecurityAutoConfiguration { public class SecurityAutoConfiguration {
/** /**
* Creates a {@link TenantAware} bean based on the given
* {@link UserAuthoritiesResolver}.
*
* @param authoritiesResolver
* The user authorities/roles resolver
*
* @return the {@link TenantAware} singleton bean which holds the current * @return the {@link TenantAware} singleton bean which holds the current
* {@link TenantAware} service and make it accessible in beans which * {@link TenantAware} service and make it accessible in beans which
* cannot access the service directly, e.g. JPA entities. * cannot access the service directly, e.g. JPA entities.
*/ */
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public TenantAware tenantAware() { public TenantAware tenantAware(final UserAuthoritiesResolver authoritiesResolver) {
return new SecurityContextTenantAware(); return new SecurityContextTenantAware(authoritiesResolver);
}
/**
* Creates a {@link UserAuthoritiesResolver} bean that is responsible for
* resolving user authorities/roles.
*
* @param securityProperties
* The Spring {@link SecurityProperties} for the security user
* @param multiUserProperties
* The {@link MultiUserProperties} for the managed users
*
* @return an {@link InMemoryUserAuthoritiesResolver} bean
*/
@Bean
@ConditionalOnMissingBean
public UserAuthoritiesResolver inMemoryAuthoritiesResolver(final SecurityProperties securityProperties,
final MultiUserProperties multiUserProperties) {
final List<User> multiUsers = multiUserProperties.getUsers();
final Map<String, List<String>> usersToPermissions;
if (!CollectionUtils.isEmpty(multiUsers)) {
usersToPermissions = multiUsers.stream().collect(Collectors.toMap(User::getUsername, User::getPermissions));
} else {
usersToPermissions = Collections.singletonMap(securityProperties.getUser().getName(),
securityProperties.getUser().getRoles());
}
return new InMemoryUserAuthoritiesResolver(usersToPermissions);
} }
/** /**

View File

@@ -42,7 +42,27 @@ public interface TenantAware {
* @throws any * @throws any
* kind of {@link RuntimeException} * kind of {@link RuntimeException}
*/ */
<T> T runAsTenant(final String tenant, TenantRunner<T> tenantRunner); <T> T runAsTenant(String tenant, TenantRunner<T> tenantRunner);
/**
* 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}. After the
* {@link TenantRunner} 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 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}
* @throws any
* kind of {@link RuntimeException}
*/
<T> T runAsTenantAsUser(String tenant, String username, TenantRunner<T> tenantRunner);
/** /**
* An {@link TenantRunner} interface which allows to run specific code under * An {@link TenantRunner} interface which allows to run specific code under

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2020 Bosch.IO GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.tenancy;
import java.util.Collection;
/**
* The service responsible for making the lookup for user authorities/roles
* based on his tenant and username
*/
@FunctionalInterface
public interface UserAuthoritiesResolver {
/**
* User authorities/roles lookup based on the tenant and the username
*
* @param tenant
* The tenant that this user belongs to
* @param username
* The username of the user
* @return a {@link Collection} of authorities/roles for this user
*/
Collection<String> getUserAuthorities(String tenant, String username);
}

View File

@@ -13,6 +13,7 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -43,6 +44,7 @@ import org.eclipse.hawkbit.security.DmfTenantSecurityToken;
import org.eclipse.hawkbit.security.DmfTenantSecurityToken.FileResource; import org.eclipse.hawkbit.security.DmfTenantSecurityToken.FileResource;
import org.eclipse.hawkbit.security.SecurityContextTenantAware; import org.eclipse.hawkbit.security.SecurityContextTenantAware;
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.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -104,10 +106,10 @@ public class AmqpControllerAuthenticationTest {
private ArtifactManagement artifactManagementMock; private ArtifactManagement artifactManagementMock;
@Mock @Mock
private ControllerManagement controllerManagementMock; private Target targetMock;
@Mock @Mock
private Target targetMock; private UserAuthoritiesResolver authoritiesResolver;
@Mock @Mock
private RabbitTemplate rabbitTemplate; private RabbitTemplate rabbitTemplate;
@@ -140,7 +142,7 @@ public class AmqpControllerAuthenticationTest {
when(tenantConfigurationManagementMock.getConfigurationValue(any(), eq(Boolean.class))) when(tenantConfigurationManagementMock.getConfigurationValue(any(), eq(Boolean.class)))
.thenReturn(CONFIG_VALUE_FALSE); .thenReturn(CONFIG_VALUE_FALSE);
final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(); final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(authoritiesResolver);
final SystemSecurityContext systemSecurityContext = new SystemSecurityContext(tenantAware); final SystemSecurityContext systemSecurityContext = new SystemSecurityContext(tenantAware);
authenticationManager = new AmqpControllerAuthentication(systemManagement, controllerManagement, authenticationManager = new AmqpControllerAuthentication(systemManagement, controllerManagement,
@@ -153,18 +155,18 @@ public class AmqpControllerAuthenticationTest {
testArtifact.setId(1L); testArtifact.setId(1L);
amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate, amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate,
mock(AmqpMessageDispatcherService.class), controllerManagementMock, new JpaEntityFactory(), mock(AmqpMessageDispatcherService.class), controllerManagement, new JpaEntityFactory(),
systemSecurityContext, tenantConfigurationManagementMock); systemSecurityContext, tenantConfigurationManagementMock);
amqpAuthenticationMessageHandlerService = new AmqpAuthenticationMessageHandler(rabbitTemplate, amqpAuthenticationMessageHandlerService = new AmqpAuthenticationMessageHandler(rabbitTemplate,
authenticationManager, artifactManagementMock, cacheMock, hostnameResolverMock, authenticationManager, artifactManagementMock, cacheMock, hostnameResolverMock, controllerManagement,
controllerManagementMock, tenantAware); tenantAware);
} }
private void mockAuthenticationWithoutPrincipal() { private void mockAuthenticationWithoutPrincipal() {
when(securityProperties.getAuthentication()).thenReturn(ddiAuthentication); lenient().when(securityProperties.getAuthentication()).thenReturn(ddiAuthentication);
when(ddiAuthentication.getAnonymous()).thenReturn(anonymous); lenient().when(ddiAuthentication.getAnonymous()).thenReturn(anonymous);
when(anonymous.isEnabled()).thenReturn(false); lenient().when(anonymous.isEnabled()).thenReturn(false);
} }
private void mockSuccessfulAuthentication() throws MalformedURLException { private void mockSuccessfulAuthentication() throws MalformedURLException {
@@ -210,13 +212,15 @@ public class AmqpControllerAuthenticationTest {
@Test @Test
@Description("Tests authentication successful") @Description("Tests authentication successful")
public void testSuccessfulAuthentication() { public void testSuccessfulAuthentication() {
when(controllerManagement.get(any(Long.class))).thenReturn(Optional.of(targetMock));
final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID, final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(TENANT, TENANT_ID, CONTROLLER_ID,
TARGET_ID, FileResource.createFileResourceBySha1(SHA1)); TARGET_ID, FileResource.createFileResourceBySha1(SHA1));
when(tenantConfigurationManagementMock.getConfigurationValue( when(tenantConfigurationManagementMock.getConfigurationValue(
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class))) eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class)))
.thenReturn(CONFIG_VALUE_TRUE); .thenReturn(CONFIG_VALUE_TRUE);
when(controllerManagement.get(any(Long.class))).thenReturn(Optional.of(targetMock));
when(targetMock.getSecurityToken()).thenReturn(CONTROLLER_ID); when(targetMock.getSecurityToken()).thenReturn(CONTROLLER_ID);
when(targetMock.getControllerId()).thenReturn(CONTROLLER_ID); when(targetMock.getControllerId()).thenReturn(CONTROLLER_ID);
@@ -257,6 +261,7 @@ public class AmqpControllerAuthenticationTest {
when(tenantConfigurationManagementMock.getConfigurationValue( when(tenantConfigurationManagementMock.getConfigurationValue(
eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class))) eq(TenantConfigurationKey.AUTHENTICATION_MODE_TARGET_SECURITY_TOKEN_ENABLED), eq(Boolean.class)))
.thenReturn(CONFIG_VALUE_TRUE); .thenReturn(CONFIG_VALUE_TRUE);
when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter); when(rabbitTemplate.getMessageConverter()).thenReturn(messageConverter);
securityToken.putHeader(DmfTenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken 12" + CONTROLLER_ID); securityToken.putHeader(DmfTenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken 12" + CONTROLLER_ID);
@@ -275,13 +280,14 @@ public class AmqpControllerAuthenticationTest {
@Test @Test
@Description("Tests authentication message successful") @Description("Tests authentication message successful")
public void successfulMessageAuthentication() throws Exception { public void successfulMessageAuthentication() throws Exception {
final MessageProperties messageProperties = createMessageProperties(null); final MessageProperties messageProperties = createMessageProperties(null);
final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(TENANT, null, CONTROLLER_ID, null, final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(TENANT, null, CONTROLLER_ID, null,
FileResource.createFileResourceBySha1(SHA1)); FileResource.createFileResourceBySha1(SHA1));
mockSuccessfulAuthentication(); mockSuccessfulAuthentication();
when(controllerManagement.getByControllerId(anyString())).thenReturn(Optional.of(targetMock)); when(controllerManagement.getByControllerId(anyString())).thenReturn(Optional.of(targetMock));
when(controllerManagementMock.hasTargetArtifactAssigned(CONTROLLER_ID, SHA1)).thenReturn(true); when(controllerManagement.hasTargetArtifactAssigned(CONTROLLER_ID, SHA1)).thenReturn(true);
when(artifactManagementMock.findFirstBySHA1(SHA1)).thenReturn(Optional.of(testArtifact)); when(artifactManagementMock.findFirstBySHA1(SHA1)).thenReturn(Optional.of(testArtifact));
securityToken.putHeader(DmfTenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLER_ID); securityToken.putHeader(DmfTenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLER_ID);
@@ -312,8 +318,8 @@ public class AmqpControllerAuthenticationTest {
mockSuccessfulAuthentication(); mockSuccessfulAuthentication();
when(controllerManagement.get(any(Long.class))).thenReturn(Optional.of(targetMock)); when(controllerManagement.get(any(Long.class))).thenReturn(Optional.of(targetMock));
when(controllerManagement.hasTargetArtifactAssigned(TARGET_ID, SHA1)).thenReturn(true);
when(artifactManagementMock.get(ARTIFACT_ID)).thenReturn(Optional.of(testArtifact)); when(artifactManagementMock.get(ARTIFACT_ID)).thenReturn(Optional.of(testArtifact));
when(controllerManagementMock.hasTargetArtifactAssigned(TARGET_ID, SHA1)).thenReturn(true);
securityToken.putHeader(DmfTenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLER_ID); securityToken.putHeader(DmfTenantSecurityToken.AUTHORIZATION_HEADER, "TargetToken " + CONTROLLER_ID);
final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken, final Message message = amqpMessageHandlerService.getMessageConverter().toMessage(securityToken,
@@ -337,6 +343,7 @@ public class AmqpControllerAuthenticationTest {
@Test @Test
@Description("Tests authentication message successful") @Description("Tests authentication message successful")
public void successfulMessageAuthenticationWithTenantId() throws Exception { public void successfulMessageAuthenticationWithTenantId() throws Exception {
final MessageProperties messageProperties = createMessageProperties(null); final MessageProperties messageProperties = createMessageProperties(null);
final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(null, TENANT_ID, CONTROLLER_ID, final DmfTenantSecurityToken securityToken = new DmfTenantSecurityToken(null, TENANT_ID, CONTROLLER_ID,
TARGET_ID, FileResource.createFileResourceBySha1(SHA1)); TARGET_ID, FileResource.createFileResourceBySha1(SHA1));
@@ -344,7 +351,7 @@ public class AmqpControllerAuthenticationTest {
mockSuccessfulAuthentication(); mockSuccessfulAuthentication();
when(controllerManagement.get(any(Long.class))).thenReturn(Optional.of(targetMock)); when(controllerManagement.get(any(Long.class))).thenReturn(Optional.of(targetMock));
when(controllerManagementMock.hasTargetArtifactAssigned(CONTROLLER_ID, SHA1)).thenReturn(true); when(controllerManagement.hasTargetArtifactAssigned(CONTROLLER_ID, SHA1)).thenReturn(true);
when(artifactManagementMock.findFirstBySHA1(SHA1)).thenReturn(Optional.of(testArtifact)); when(artifactManagementMock.findFirstBySHA1(SHA1)).thenReturn(Optional.of(testArtifact));
when(tenantMetaData.getTenant()).thenReturn(TENANT); when(tenantMetaData.getTenant()).thenReturn(TENANT);
when(systemManagement.getTenantMetadata(TENANT_ID)).thenReturn(tenantMetaData); when(systemManagement.getTenantMetadata(TENANT_ID)).thenReturn(tenantMetaData);

View File

@@ -60,6 +60,7 @@ import org.eclipse.hawkbit.security.SecurityContextTenantAware;
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.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;
@@ -133,6 +134,9 @@ public class AmqpMessageHandlerServiceTest {
@Mock @Mock
private TenantAware tenantAwareMock; private TenantAware tenantAwareMock;
@Mock
private UserAuthoritiesResolver authoritiesResolver;
@Captor @Captor
private ArgumentCaptor<Map<String, String>> attributesCaptor; private ArgumentCaptor<Map<String, String>> attributesCaptor;
@@ -155,7 +159,7 @@ public class AmqpMessageHandlerServiceTest {
lenient().when(tenantConfigurationManagement.getConfigurationValue(MULTI_ASSIGNMENTS_ENABLED, Boolean.class)) lenient().when(tenantConfigurationManagement.getConfigurationValue(MULTI_ASSIGNMENTS_ENABLED, Boolean.class))
.thenReturn(multiAssignmentConfig); .thenReturn(multiAssignmentConfig);
final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(); final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(authoritiesResolver);
final SystemSecurityContext systemSecurityContext = new SystemSecurityContext(tenantAware); final SystemSecurityContext systemSecurityContext = new SystemSecurityContext(tenantAware);
amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate, amqpMessageDispatcherServiceMock, amqpMessageHandlerService = new AmqpMessageHandlerService(rabbitTemplate, amqpMessageDispatcherServiceMock,

View File

@@ -14,6 +14,7 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -62,6 +63,7 @@ import org.eclipse.hawkbit.repository.jpa.utils.WeightValidationHelper;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType; import org.eclipse.hawkbit.repository.model.Action.ActionType;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.BaseEntity;
import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSet;
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.repository.model.Rollout.RolloutStatus;
@@ -564,8 +566,7 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
try { try {
long actionsCreated; long actionsCreated;
do { do {
actionsCreated = createActionsForTargetsInNewTransaction(rollout.getId(), group.getId(), actionsCreated = createActionsForTargetsInNewTransaction(rollout.getId(), group.getId(), TRANSACTION_TARGETS);
TRANSACTION_TARGETS);
totalActionsCreated += actionsCreated; totalActionsCreated += actionsCreated;
} while (actionsCreated > 0); } while (actionsCreated > 0);
@@ -576,7 +577,8 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
return totalActionsCreated; return totalActionsCreated;
} }
private Long createActionsForTargetsInNewTransaction(final long rolloutId, final long groupId, final int limit) { private Long createActionsForTargetsInNewTransaction(final long rolloutId, final long groupId,
final int limit) {
return DeploymentHelper.runInNewTransaction(txManager, "createActionsForTargets", status -> { return DeploymentHelper.runInNewTransaction(txManager, "createActionsForTargets", status -> {
final PageRequest pageRequest = PageRequest.of(0, limit); final PageRequest pageRequest = PageRequest.of(0, limit);
final Rollout rollout = rolloutRepository.findById(rolloutId) final Rollout rollout = rolloutRepository.findById(rolloutId)
@@ -828,17 +830,21 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
try { try {
rollouts.forEach(rolloutId -> DeploymentHelper.runInNewTransaction(txManager, handlerId + "-" + rolloutId, rollouts.forEach(rolloutId -> DeploymentHelper.runInNewTransaction(txManager, handlerId + "-" + rolloutId,
status -> executeFittingHandler(rolloutId))); status -> handleRollout(rolloutId)));
} finally { } finally {
lock.unlock(); lock.unlock();
} }
} }
private long executeFittingHandler(final long rolloutId) { private long handleRollout(final long rolloutId) {
LOGGER.debug("handle rollout {}", rolloutId);
final JpaRollout rollout = rolloutRepository.findById(rolloutId) final JpaRollout rollout = rolloutRepository.findById(rolloutId)
.orElseThrow(() -> new EntityNotFoundException(Rollout.class, rolloutId)); .orElseThrow(() -> new EntityNotFoundException(Rollout.class, rolloutId));
runInUserContext(rollout, () -> handleRollout(rollout));
return 0;
}
private void handleRollout(final JpaRollout rollout) {
LOGGER.debug("Handle rollout {}", rollout.getId());
switch (rollout.getStatus()) { switch (rollout.getStatus()) {
case CREATING: case CREATING:
handleCreateRollout(rollout); handleCreateRollout(rollout);
@@ -859,8 +865,6 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
LOGGER.error("Rollout in status {} not supposed to be handled!", rollout.getStatus()); LOGGER.error("Rollout in status {} not supposed to be handled!", rollout.getStatus());
break; break;
} }
return 0;
} }
private void handleStartingRollout(final Rollout rollout) { private void handleStartingRollout(final Rollout rollout) {
@@ -1147,4 +1151,9 @@ public class JpaRolloutManagement extends AbstractRolloutManagement {
QuotaHelper.assertAssignmentQuota(target.getId(), requested, quota, Action.class, Target.class, QuotaHelper.assertAssignmentQuota(target.getId(), requested, quota, Action.class, Target.class,
actionRepository::countByTargetId); actionRepository::countByTargetId);
} }
private void runInUserContext(final BaseEntity rollout, final Runnable handler) {
DeploymentHelper.runInNonSystemContext(handler, () -> Objects.requireNonNull(rollout.getCreatedBy()), tenantAware);
}
} }

View File

@@ -760,9 +760,9 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
@ConditionalOnMissingBean @ConditionalOnMissingBean
AutoAssignExecutor autoAssignExecutor(final TargetFilterQueryManagement targetFilterQueryManagement, AutoAssignExecutor autoAssignExecutor(final TargetFilterQueryManagement targetFilterQueryManagement,
final TargetManagement targetManagement, final DeploymentManagement deploymentManagement, final TargetManagement targetManagement, final DeploymentManagement deploymentManagement,
final PlatformTransactionManager transactionManager) { final PlatformTransactionManager transactionManager, final TenantAware tenantAware) {
return new AutoAssignChecker(targetFilterQueryManagement, targetManagement, deploymentManagement, return new AutoAssignChecker(targetFilterQueryManagement, targetManagement, deploymentManagement,
transactionManager); transactionManager, tenantAware);
} }
/** /**

View File

@@ -9,6 +9,7 @@
package org.eclipse.hawkbit.repository.jpa.autoassign; package org.eclipse.hawkbit.repository.jpa.autoassign;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.persistence.PersistenceException; import javax.persistence.PersistenceException;
@@ -24,6 +25,7 @@ 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.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
@@ -45,14 +47,6 @@ public class AutoAssignChecker implements AutoAssignExecutor {
private static final Logger LOGGER = LoggerFactory.getLogger(AutoAssignChecker.class); private static final Logger LOGGER = LoggerFactory.getLogger(AutoAssignChecker.class);
private final TargetFilterQueryManagement targetFilterQueryManagement;
private final TargetManagement targetManagement;
private final DeploymentManagement deploymentManagement;
private final PlatformTransactionManager transactionManager;
/** /**
* Maximum for target filter queries with auto assign DS Maximum for targets * Maximum for target filter queries with auto assign DS Maximum for targets
* that are fetched in one turn * that are fetched in one turn
@@ -65,6 +59,16 @@ public class AutoAssignChecker implements AutoAssignExecutor {
*/ */
private static final String ACTION_MESSAGE = "Auto assignment by target filter: %s"; private static final String ACTION_MESSAGE = "Auto assignment by target filter: %s";
private final TargetFilterQueryManagement targetFilterQueryManagement;
private final TargetManagement targetManagement;
private final DeploymentManagement deploymentManagement;
private final PlatformTransactionManager transactionManager;
private final TenantAware tenantAware;
/** /**
* Instantiates a new auto assign checker * Instantiates a new auto assign checker
* *
@@ -76,14 +80,17 @@ public class AutoAssignChecker implements AutoAssignExecutor {
* to assign distribution sets to targets * to assign distribution sets to targets
* @param transactionManager * @param transactionManager
* to run transactions * to run transactions
* @param tenantAware
* to handle the tenant context
*/ */
public AutoAssignChecker(final TargetFilterQueryManagement targetFilterQueryManagement, public AutoAssignChecker(final TargetFilterQueryManagement targetFilterQueryManagement,
final TargetManagement targetManagement, final DeploymentManagement deploymentManagement, final TargetManagement targetManagement, final DeploymentManagement deploymentManagement,
final PlatformTransactionManager transactionManager) { final PlatformTransactionManager transactionManager, final TenantAware tenantAware) {
this.targetFilterQueryManagement = targetFilterQueryManagement; this.targetFilterQueryManagement = targetFilterQueryManagement;
this.targetManagement = targetManagement; this.targetManagement = targetManagement;
this.deploymentManagement = deploymentManagement; this.deploymentManagement = deploymentManagement;
this.transactionManager = transactionManager; this.transactionManager = transactionManager;
this.tenantAware = tenantAware;
} }
@Override @Override
@@ -95,10 +102,9 @@ public class AutoAssignChecker implements AutoAssignExecutor {
final Page<TargetFilterQuery> filterQueries = targetFilterQueryManagement.findWithAutoAssignDS(pageRequest); final Page<TargetFilterQuery> filterQueries = targetFilterQueryManagement.findWithAutoAssignDS(pageRequest);
// we should ensure that the filter queries are executed // make sure the filter queries are executed in the order of weights
// in the order of weights
for (final TargetFilterQuery filterQuery : filterQueries) { for (final TargetFilterQuery filterQuery : filterQueries) {
checkByTargetFilterQueryAndAssignDS(filterQuery); runInUserContext(filterQuery, () -> checkByTargetFilterQueryAndAssignDS(filterQuery));
} }
} }
@@ -154,12 +160,6 @@ public class AutoAssignChecker implements AutoAssignExecutor {
}); });
} }
private static String getAutoAssignmentInitiatedBy(final TargetFilterQuery targetFilterQuery) {
return StringUtils.isEmpty(targetFilterQuery.getAutoAssignInitiatedBy()) ?
targetFilterQuery.getCreatedBy() :
targetFilterQuery.getAutoAssignInitiatedBy();
}
/** /**
* Gets all matching targets with the designated action from the target * Gets all matching targets with the designated action from the target
* management * management
@@ -168,8 +168,6 @@ public class AutoAssignChecker implements AutoAssignExecutor {
* the query the targets have to match * the query the targets have to match
* @param dsId * @param dsId
* dsId the targets are not allowed to have in their action history * dsId the targets are not allowed to have in their action history
* @param type
* action type for targets auto assignment
* @param count * @param count
* maximum amount of targets to retrieve * maximum amount of targets to retrieve
* @return list of targets with action type * @return list of targets with action type
@@ -186,4 +184,15 @@ public class AutoAssignChecker implements AutoAssignExecutor {
.setActionType(autoAssignActionType).setWeight(weight).build()).collect(Collectors.toList()); .setActionType(autoAssignActionType).setWeight(weight).build()).collect(Collectors.toList());
} }
private void runInUserContext(final TargetFilterQuery targetFilterQuery, final Runnable handler) {
DeploymentHelper.runInNonSystemContext(handler,
() -> Objects.requireNonNull(getAutoAssignmentInitiatedBy(targetFilterQuery)), tenantAware);
}
private static String getAutoAssignmentInitiatedBy(final TargetFilterQuery targetFilterQuery) {
return StringUtils.isEmpty(targetFilterQuery.getAutoAssignInitiatedBy()) ?
targetFilterQuery.getCreatedBy() :
targetFilterQuery.getAutoAssignInitiatedBy();
}
} }

View File

@@ -9,6 +9,7 @@
package org.eclipse.hawkbit.repository.jpa.utils; package org.eclipse.hawkbit.repository.jpa.utils;
import java.util.List; import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@@ -20,12 +21,17 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.Status; import org.eclipse.hawkbit.repository.model.Action.Status;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.security.SecurityContextTenantAware;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.support.DefaultTransactionDefinition; import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.StringUtils;
/** /**
* Utility class for deployment related topics. * Utility class for deployment related topics.
@@ -33,6 +39,8 @@ import org.springframework.transaction.support.TransactionTemplate;
*/ */
public final class DeploymentHelper { public final class DeploymentHelper {
private static final Logger LOG = LoggerFactory.getLogger(DeploymentHelper.class);
private DeploymentHelper() { private DeploymentHelper() {
// utility class // utility class
} }
@@ -110,4 +118,37 @@ public final class DeploymentHelper {
def.setIsolationLevel(isolationLevel); def.setIsolationLevel(isolationLevel);
return new TransactionTemplate(txManager, def).execute(action); return new TransactionTemplate(txManager, def).execute(action);
} }
/**
* Runs the given handler in a non-system user context. Switches to the user
* which is provided by the given callback.
*
* @param handler
* The handler to be invoked in the right user context.
* @param username
* Callback to obtain the real user the user context should be
* established for.
* @param tenantAware
* The {@link TenantAware} bean to determine the current tenant
* context.
*/
public static void runInNonSystemContext(@NotNull final Runnable handler, @NotNull final Supplier<String> username,
@NotNull final TenantAware tenantAware) {
final String currentUser = tenantAware.getCurrentUsername();
if (isNonSystemUser(currentUser)) {
handler.run();
return;
}
final String user = username.get();
LOG.debug("Switching user context from '{}' to '{}'", currentUser, user);
tenantAware.runAsTenantAsUser(tenantAware.getCurrentTenant(), user, () -> {
handler.run();
return null;
});
}
private static boolean isNonSystemUser(final String user) {
return (!(StringUtils.isEmpty(user) || SecurityContextTenantAware.SYSTEM_USER.equals(user)));
}
} }

View File

@@ -8,6 +8,7 @@
*/ */
package org.eclipse.hawkbit.repository.test; package org.eclipse.hawkbit.repository.test;
import java.util.Collections;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
@@ -38,11 +39,13 @@ import org.eclipse.hawkbit.security.SecurityTokenGenerator;
import org.eclipse.hawkbit.security.SpringSecurityAuditorAware; import org.eclipse.hawkbit.security.SpringSecurityAuditorAware;
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.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler; import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCacheManager; import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.cloud.bus.ConditionalOnBusEnabled; import org.springframework.cloud.bus.ConditionalOnBusEnabled;
import org.springframework.context.ApplicationEvent; import org.springframework.context.ApplicationEvent;
@@ -118,23 +121,30 @@ public class TestConfiguration implements AsyncConfigurer {
} }
@Bean @Bean
TenantAware tenantAware() { UserAuthoritiesResolver authoritiesResolver() {
return new SecurityContextTenantAware(); return (tenant, username) -> Collections.emptyList();
} }
@Bean @Bean
TenantAwareCacheManager cacheManager() { TenantAware tenantAware(final UserAuthoritiesResolver authoritiesResolver) {
return new TenantAwareCacheManager(new CaffeineCacheManager(), tenantAware()); return new SecurityContextTenantAware(authoritiesResolver);
}
@Bean
TenantAwareCacheManager cacheManager(final TenantAware tenantAware) {
return new TenantAwareCacheManager(new CaffeineCacheManager(), tenantAware);
} }
/** /**
* Bean for the download id cache. * Bean for the download id cache.
* *
* @param cacheManager
* The {@link CacheManager}
* @return the cache * @return the cache
*/ */
@Bean @Bean
DownloadIdCache downloadIdCache() { DownloadIdCache downloadIdCache(final CacheManager cacheManager) {
return new DefaultDownloadIdCache(cacheManager()); return new DefaultDownloadIdCache(cacheManager);
} }
@Bean(name = AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME) @Bean(name = AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME)

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2020 Bosch.IO GmbH and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.hawkbit.security;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver;
/**
* An implementation of the {@link UserAuthoritiesResolver} that is based on
* in-memory user permissions.
*/
public class InMemoryUserAuthoritiesResolver implements UserAuthoritiesResolver {
private final Map<String, List<String>> usernamesToAuthorities;
/**
* Constructs the resolver based on the given authority lookup map.
*
* @param usernamesToAuthorities
* The authority map to read from. Must not be <code>null</code>.
*/
public InMemoryUserAuthoritiesResolver(final Map<String, List<String>> usernamesToAuthorities) {
this.usernamesToAuthorities = usernamesToAuthorities;
}
@Override
public Collection<String> getUserAuthorities(final String tenant, final String username) {
// we can ignore the tenant here (no multi-tenancy by default)
final Collection<String> authorities = usernamesToAuthorities.get(username);
if (authorities == null) {
return Collections.emptyList();
}
return authorities;
}
}

View File

@@ -8,14 +8,16 @@
*/ */
package org.eclipse.hawkbit.security; package org.eclipse.hawkbit.security;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions; import org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions;
import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails; import org.eclipse.hawkbit.im.authentication.TenantAwareAuthenticationDetails;
import org.eclipse.hawkbit.im.authentication.UserPrincipal; import org.eclipse.hawkbit.im.authentication.UserPrincipal;
import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver;
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.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority;
@@ -24,14 +26,32 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.core.context.SecurityContextImpl;
/** /**
* A {@link TenantAware} implemenation which retrieves the ID of the tenant from * A {@link TenantAware} implementation which retrieves the ID of the tenant
* the {@link SecurityContext#getAuthentication()} * from the {@link SecurityContext#getAuthentication()}
* {@link Authentication#getDetails()} which holds the * {@link Authentication#getDetails()} which holds the
* {@link TenantAwareAuthenticationDetails} object. * {@link TenantAwareAuthenticationDetails} object.
* *
*/ */
public class SecurityContextTenantAware implements TenantAware { public class SecurityContextTenantAware implements TenantAware {
public static final String SYSTEM_USER = "system";
private static final Collection<? extends GrantedAuthority> SYSTEM_AUTHORITIES = Collections
.singletonList(new SimpleGrantedAuthority(SpringEvalExpressions.SYSTEM_ROLE));
private final UserAuthoritiesResolver authoritiesResolver;
/**
* Creates the {@link SecurityContextTenantAware} based on the given
* {@link UserAuthoritiesResolver}.
*
* @param authoritiesResolver
* Resolver to retrieve the authorities for a given user. Must
* not be <code>null</code>.
*/
public SecurityContextTenantAware(final UserAuthoritiesResolver authoritiesResolver) {
this.authoritiesResolver = authoritiesResolver;
}
@Override @Override
public String getCurrentTenant() { public String getCurrentTenant() {
final SecurityContext context = SecurityContextHolder.getContext(); final SecurityContext context = SecurityContextHolder.getContext();
@@ -59,44 +79,68 @@ public class SecurityContextTenantAware implements TenantAware {
} }
@Override @Override
public <T> T runAsTenant(final String tenant, final TenantRunner<T> callable) { public <T> T runAsTenant(final String tenant, final TenantRunner<T> tenantRunner) {
return runInContext(buildSystemSecurityContext(tenant), tenantRunner);
}
@Override
public <T> T runAsTenantAsUser(final String tenant, final String username, final TenantRunner<T> tenantRunner) {
final List<SimpleGrantedAuthority> authorities = runAsSystem(
() -> authoritiesResolver.getUserAuthorities(tenant, username).stream().map(SimpleGrantedAuthority::new)
.collect(Collectors.toList()));
return runInContext(buildUserSecurityContext(tenant, username, authorities), tenantRunner);
}
private static <T> T runInContext(final SecurityContext context, final TenantRunner<T> tenantRunner) {
final SecurityContext originalContext = SecurityContextHolder.getContext(); final SecurityContext originalContext = SecurityContextHolder.getContext();
try { try {
SecurityContextHolder.setContext(buildSecurityContext(tenant)); SecurityContextHolder.setContext(context);
return callable.run(); return tenantRunner.run();
} finally { } finally {
SecurityContextHolder.setContext(originalContext); SecurityContextHolder.setContext(originalContext);
} }
} }
private static SecurityContext buildSecurityContext(final String tenant) { private static SecurityContext buildSystemSecurityContext(final String tenant) {
return buildUserSecurityContext(tenant, SYSTEM_USER, SYSTEM_AUTHORITIES);
}
private static <T> T runAsSystem(final TenantRunner<T> tenantRunner) {
final SecurityContext currentContext = SecurityContextHolder.getContext();
try {
SystemSecurityContext.setSystemContext(currentContext);
return tenantRunner.run();
} finally {
SecurityContextHolder.setContext(currentContext);
}
}
private static SecurityContext buildUserSecurityContext(final String tenant, final String username,
final Collection<? extends GrantedAuthority> authorities) {
final SecurityContextImpl securityContext = new SecurityContextImpl(); final SecurityContextImpl securityContext = new SecurityContextImpl();
securityContext.setAuthentication( securityContext.setAuthentication(new AuthenticationDelegate(
new AuthenticationDelegate(SecurityContextHolder.getContext().getAuthentication(), tenant)); SecurityContextHolder.getContext().getAuthentication(), tenant, username, authorities));
return securityContext; return securityContext;
} }
/** /**
* An {@link Authentication} implementation to delegate to an existing * An {@link Authentication} implementation to delegate to an existing
* {@link Authentication} object except setting the details specifically for * {@link Authentication} object except setting the details specifically for
* a specific tenant. * a specific tenant and user.
*/ */
private static final class AuthenticationDelegate implements Authentication { private static final class AuthenticationDelegate implements Authentication {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final String SYSTEM_USER = "system";
private static final Collection<? extends GrantedAuthority> SYSTEM_AUTHORITIES = Arrays
.asList(new SimpleGrantedAuthority(SpringEvalExpressions.SYSTEM_ROLE));
private final Authentication delegate; private final Authentication delegate;
private final UserPrincipal systemPrincipal; private final UserPrincipal principal;
private final TenantAwareAuthenticationDetails tenantAwareAuthenticationDetails; private final TenantAwareAuthenticationDetails tenantAwareAuthenticationDetails;
private AuthenticationDelegate(final Authentication delegate, final String tenant) { private AuthenticationDelegate(final Authentication delegate, final String tenant, final String username,
final Collection<? extends GrantedAuthority> authorities) {
this.delegate = delegate; this.delegate = delegate;
this.systemPrincipal = new UserPrincipal(SYSTEM_USER, SYSTEM_USER, SYSTEM_USER, SYSTEM_USER, SYSTEM_USER, this.principal = new UserPrincipal(username, username, null, null, username, null, tenant, authorities);
null, tenant, SYSTEM_AUTHORITIES);
tenantAwareAuthenticationDetails = new TenantAwareAuthenticationDetails(tenant, false); tenantAwareAuthenticationDetails = new TenantAwareAuthenticationDetails(tenant, false);
} }
@@ -112,27 +156,27 @@ public class SecurityContextTenantAware implements TenantAware {
@Override @Override
public String toString() { public String toString() {
return (delegate != null) ? delegate.toString() : null; return delegate != null ? delegate.toString() : null;
} }
@Override @Override
public int hashCode() { public int hashCode() {
return (delegate != null) ? delegate.hashCode() : -1; return delegate != null ? delegate.hashCode() : -1;
} }
@Override @Override
public String getName() { public String getName() {
return (delegate != null) ? delegate.getName() : null; return delegate != null ? delegate.getName() : null;
} }
@Override @Override
public Collection<? extends GrantedAuthority> getAuthorities() { public Collection<? extends GrantedAuthority> getAuthorities() {
return (delegate != null) ? delegate.getAuthorities() : Collections.emptyList(); return delegate != null ? delegate.getAuthorities() : Collections.emptyList();
} }
@Override @Override
public Object getCredentials() { public Object getCredentials() {
return (delegate != null) ? delegate.getCredentials() : null; return delegate != null ? delegate.getCredentials() : null;
} }
@Override @Override
@@ -142,7 +186,7 @@ public class SecurityContextTenantAware implements TenantAware {
@Override @Override
public Object getPrincipal() { public Object getPrincipal() {
return systemPrincipal; return principal;
} }
@Override @Override

View File

@@ -171,7 +171,7 @@ public class SystemSecurityContext {
SecurityContextHolder.setContext(securityContextImpl); SecurityContextHolder.setContext(securityContextImpl);
} }
private static void setSystemContext(final SecurityContext oldContext) { static void setSystemContext(final SecurityContext oldContext) {
final Authentication oldAuthentication = oldContext.getAuthentication(); final Authentication oldAuthentication = oldContext.getAuthentication();
final SecurityContextImpl securityContextImpl = new SecurityContextImpl(); final SecurityContextImpl securityContextImpl = new SecurityContextImpl();
securityContextImpl.setAuthentication(new SystemCodeAuthentication(oldAuthentication)); securityContextImpl.setAuthentication(new SystemCodeAuthentication(oldAuthentication));

View File

@@ -17,6 +17,7 @@ import java.util.Collection;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement; import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue; import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
import org.eclipse.hawkbit.security.DmfTenantSecurityToken.FileResource; import org.eclipse.hawkbit.security.DmfTenantSecurityToken.FileResource;
import org.eclipse.hawkbit.tenancy.UserAuthoritiesResolver;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey; import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@@ -33,16 +34,6 @@ import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
public class ControllerPreAuthenticatedSecurityHeaderFilterTest { public class ControllerPreAuthenticatedSecurityHeaderFilterTest {
private ControllerPreAuthenticatedSecurityHeaderFilter underTest;
@Mock
private TenantConfigurationManagement tenantConfigurationManagementMock;
@Mock
private DmfTenantSecurityToken tenantSecurityTokenMock;
private final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware();
private static final String CA_COMMON_NAME = "ca-cn"; private static final String CA_COMMON_NAME = "ca-cn";
private static final String CA_COMMON_NAME_VALUE = "box1"; private static final String CA_COMMON_NAME_VALUE = "box1";
@@ -61,8 +52,18 @@ public class ControllerPreAuthenticatedSecurityHeaderFilterTest {
private static final TenantConfigurationValue<String> CONFIG_VALUE_MULTI_HASH = TenantConfigurationValue private static final TenantConfigurationValue<String> CONFIG_VALUE_MULTI_HASH = TenantConfigurationValue
.<String> builder().value(MULTI_HASH).build(); .<String> builder().value(MULTI_HASH).build();
private ControllerPreAuthenticatedSecurityHeaderFilter underTest;
@Mock
private TenantConfigurationManagement tenantConfigurationManagementMock;
@Mock
private DmfTenantSecurityToken tenantSecurityTokenMock;
@Mock
private UserAuthoritiesResolver authoritiesResolver;
@BeforeEach @BeforeEach
public void before() { public void before() {
final SecurityContextTenantAware tenantAware = new SecurityContextTenantAware(authoritiesResolver);
underTest = new ControllerPreAuthenticatedSecurityHeaderFilter(CA_COMMON_NAME, "X-Ssl-Issuer-Hash-%d", underTest = new ControllerPreAuthenticatedSecurityHeaderFilter(CA_COMMON_NAME, "X-Ssl-Issuer-Hash-%d",
tenantConfigurationManagementMock, tenantAware, new SystemSecurityContext(tenantAware)); tenantConfigurationManagementMock, tenantAware, new SystemSecurityContext(tenantAware));
} }