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:
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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));
|
||||||
|
|||||||
@@ -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));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user