Fine-grained permissions (#2535)
* Fine-grained permissions Adds support for permissions of type <permission>(/<rsql filter scope>) Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com> * Apply review fixes --------- Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -9,7 +9,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.autoconfigure.repository;
|
package org.eclipse.hawkbit.autoconfigure.repository;
|
||||||
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration;
|
import org.eclipse.hawkbit.repository.jpa.JpaRepositoryConfiguration;
|
||||||
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
|
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyReplacer;
|
||||||
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyResolver;
|
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyResolver;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||||
@@ -22,8 +22,8 @@ import org.springframework.context.annotation.Import;
|
|||||||
* Auto-Configuration for enabling JPA repository.
|
* Auto-Configuration for enabling JPA repository.
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@ConditionalOnClass({ RepositoryApplicationConfiguration.class })
|
@ConditionalOnClass({ JpaRepositoryConfiguration.class })
|
||||||
@Import({ RepositoryApplicationConfiguration.class })
|
@Import({ JpaRepositoryConfiguration.class })
|
||||||
public class JpaRepositoryAutoConfiguration {
|
public class JpaRepositoryAutoConfiguration {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ public class ExecutorAutoConfiguration {
|
|||||||
/**
|
/**
|
||||||
* @return ExecutorService with security context availability in thread execution.
|
* @return ExecutorService with security context availability in thread execution.
|
||||||
*/
|
*/
|
||||||
@Bean(destroyMethod = "shutdown")
|
@Bean(name = "asyncExecutor", destroyMethod = "shutdown")
|
||||||
@ConditionalOnMissingBean
|
@ConditionalOnMissingBean
|
||||||
public ExecutorService asyncExecutor() {
|
public ExecutorService asyncExecutor() {
|
||||||
return new DelegatingSecurityContextExecutorService(threadPoolExecutor());
|
return new DelegatingSecurityContextExecutorService(threadPoolExecutor());
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import java.util.stream.Collectors;
|
|||||||
import org.eclipse.hawkbit.ContextAware;
|
import org.eclipse.hawkbit.ContextAware;
|
||||||
import org.eclipse.hawkbit.audit.AuditContextProvider;
|
import org.eclipse.hawkbit.audit.AuditContextProvider;
|
||||||
import org.eclipse.hawkbit.audit.AuditLoggingAspect;
|
import org.eclipse.hawkbit.audit.AuditLoggingAspect;
|
||||||
import org.eclipse.hawkbit.im.authentication.SpRole;
|
import org.eclipse.hawkbit.repository.RepositoryConfiguration;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAware.DefaultTenantResolver;
|
import org.eclipse.hawkbit.tenancy.TenantAware.DefaultTenantResolver;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAware.TenantResolver;
|
import org.eclipse.hawkbit.tenancy.TenantAware.TenantResolver;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAwareUserProperties;
|
import org.eclipse.hawkbit.tenancy.TenantAwareUserProperties;
|
||||||
@@ -39,11 +39,9 @@ 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;
|
||||||
|
import org.springframework.context.annotation.Import;
|
||||||
import org.springframework.data.domain.AuditorAware;
|
import org.springframework.data.domain.AuditorAware;
|
||||||
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
|
||||||
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
|
||||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
||||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
|
|
||||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
|
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
|
||||||
import org.springframework.security.web.authentication.logout.LogoutHandler;
|
import org.springframework.security.web.authentication.logout.LogoutHandler;
|
||||||
@@ -57,6 +55,7 @@ import org.springframework.util.CollectionUtils;
|
|||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableConfigurationProperties({ SecurityProperties.class, HawkbitSecurityProperties.class, TenantAwareUserProperties.class })
|
@EnableConfigurationProperties({ SecurityProperties.class, HawkbitSecurityProperties.class, TenantAwareUserProperties.class })
|
||||||
|
@Import(RepositoryConfiguration.class)
|
||||||
public class SecurityAutoConfiguration {
|
public class SecurityAutoConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@@ -170,19 +169,4 @@ public class SecurityAutoConfiguration {
|
|||||||
simpleUrlLogoutSuccessHandler.setTargetUrlParameter("login");
|
simpleUrlLogoutSuccessHandler.setTargetUrlParameter("login");
|
||||||
return simpleUrlLogoutSuccessHandler;
|
return simpleUrlLogoutSuccessHandler;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
static RoleHierarchy roleHierarchy() {
|
|
||||||
return RoleHierarchyImpl.fromHierarchy(SpRole.DEFAULT_ROLE_HIERARCHY);
|
|
||||||
}
|
|
||||||
|
|
||||||
// and, if using method security also add
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
static MethodSecurityExpressionHandler methodSecurityExpressionHandler(final RoleHierarchy roleHierarchy) {
|
|
||||||
final DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
|
|
||||||
expressionHandler.setRoleHierarchy(roleHierarchy);
|
|
||||||
return expressionHandler;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -34,10 +34,6 @@
|
|||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.hateoas</groupId>
|
|
||||||
<artifactId>spring-hateoas</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springdoc</groupId>
|
<groupId>org.springdoc</groupId>
|
||||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import org.eclipse.hawkbit.ddi.json.model.DdiConfirmationFeedback;
|
|||||||
import org.eclipse.hawkbit.ddi.json.model.DdiProgress;
|
import org.eclipse.hawkbit.ddi.json.model.DdiProgress;
|
||||||
import org.eclipse.hawkbit.ddi.json.model.DdiResult;
|
import org.eclipse.hawkbit.ddi.json.model.DdiResult;
|
||||||
import org.eclipse.hawkbit.ddi.json.model.DdiStatus;
|
import org.eclipse.hawkbit.ddi.json.model.DdiStatus;
|
||||||
import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration;
|
import org.eclipse.hawkbit.repository.jpa.JpaRepositoryConfiguration;
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
|
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
|
||||||
import org.eclipse.hawkbit.repository.model.Action;
|
import org.eclipse.hawkbit.repository.model.Action;
|
||||||
import org.eclipse.hawkbit.repository.model.Artifact;
|
import org.eclipse.hawkbit.repository.model.Artifact;
|
||||||
@@ -55,7 +55,7 @@ import org.springframework.test.web.servlet.ResultMatcher;
|
|||||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
|
||||||
|
|
||||||
@ContextConfiguration(
|
@ContextConfiguration(
|
||||||
classes = { DdiApiConfiguration.class, RestConfiguration.class, RepositoryApplicationConfiguration.class, TestConfiguration.class })
|
classes = { DdiApiConfiguration.class, RestConfiguration.class, JpaRepositoryConfiguration.class, TestConfiguration.class })
|
||||||
@TestPropertySource(locations = "classpath:/ddi-test.properties")
|
@TestPropertySource(locations = "classpath:/ddi-test.properties")
|
||||||
public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrationTest {
|
public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrationTest {
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ package org.eclipse.hawkbit.ddi.rest.resource;
|
|||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS;
|
import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS;
|
||||||
import static org.eclipse.hawkbit.im.authentication.SpPermission.TENANT_CONFIGURATION;
|
import static org.eclipse.hawkbit.im.authentication.SpPermission.TENANT_CONFIGURATION;
|
||||||
|
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.callAs;
|
||||||
|
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.getAs;
|
||||||
|
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.withController;
|
||||||
|
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.withUser;
|
||||||
import static org.hamcrest.CoreMatchers.equalTo;
|
import static org.hamcrest.CoreMatchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||||
@@ -55,7 +59,6 @@ import org.eclipse.hawkbit.repository.model.Target;
|
|||||||
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
||||||
import org.eclipse.hawkbit.repository.test.matcher.Expect;
|
import org.eclipse.hawkbit.repository.test.matcher.Expect;
|
||||||
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
|
import org.eclipse.hawkbit.repository.test.matcher.ExpectEvents;
|
||||||
import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch;
|
|
||||||
import org.eclipse.hawkbit.repository.test.util.WithUser;
|
import org.eclipse.hawkbit.repository.test.util.WithUser;
|
||||||
import org.eclipse.hawkbit.rest.util.JsonBuilder;
|
import org.eclipse.hawkbit.rest.util.JsonBuilder;
|
||||||
import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter;
|
import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter;
|
||||||
@@ -126,7 +129,7 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
|||||||
final Target findTargetByControllerID = targetManagement.getByControllerID(knownTargetControllerId).get();
|
final Target findTargetByControllerID = targetManagement.getByControllerID(knownTargetControllerId).get();
|
||||||
assertThat(findTargetByControllerID.getCreatedBy()).isEqualTo(knownCreatedBy);
|
assertThat(findTargetByControllerID.getCreatedBy()).isEqualTo(knownCreatedBy);
|
||||||
// make a poll, audit information should not be changed, run as controller principal!
|
// make a poll, audit information should not be changed, run as controller principal!
|
||||||
SecurityContextSwitch.runAs(SecurityContextSwitch.withController("controller", CONTROLLER_ROLE_ANONYMOUS),
|
callAs(withController("controller", CONTROLLER_ROLE_ANONYMOUS),
|
||||||
() -> {
|
() -> {
|
||||||
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), knownTargetControllerId))
|
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), knownTargetControllerId))
|
||||||
.andDo(MockMvcResultPrinter.print())
|
.andDo(MockMvcResultPrinter.print())
|
||||||
@@ -198,8 +201,8 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
|||||||
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1),
|
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1),
|
||||||
@Expect(type = TenantConfigurationDeletedEvent.class, count = 1) })
|
@Expect(type = TenantConfigurationDeletedEvent.class, count = 1) })
|
||||||
void pollWithModifiedGlobalPollingTime() throws Exception {
|
void pollWithModifiedGlobalPollingTime() throws Exception {
|
||||||
withPollingTime("00:02:00", () -> SecurityContextSwitch.runAs(
|
withPollingTime("00:02:00", () -> callAs(
|
||||||
SecurityContextSwitch.withUser("controller", CONTROLLER_ROLE_ANONYMOUS),
|
withUser("controller", CONTROLLER_ROLE_ANONYMOUS),
|
||||||
() -> {
|
() -> {
|
||||||
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), 4711))
|
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), 4711))
|
||||||
.andDo(MockMvcResultPrinter.print())
|
.andDo(MockMvcResultPrinter.print())
|
||||||
@@ -221,8 +224,8 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
|||||||
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1),
|
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1),
|
||||||
@Expect(type = TenantConfigurationDeletedEvent.class, count = 1) })
|
@Expect(type = TenantConfigurationDeletedEvent.class, count = 1) })
|
||||||
void pollWithModifiedWithOverridesGlobalPollingTime() throws Exception {
|
void pollWithModifiedWithOverridesGlobalPollingTime() throws Exception {
|
||||||
withPollingTime("00:02:00, controllerid == 4711 -> 00:01:00", () -> SecurityContextSwitch.runAs(
|
withPollingTime("00:02:00, controllerid == 4711 -> 00:01:00", () -> callAs(
|
||||||
SecurityContextSwitch.withUser("controller", CONTROLLER_ROLE_ANONYMOUS),
|
withUser("controller", CONTROLLER_ROLE_ANONYMOUS),
|
||||||
() -> {
|
() -> {
|
||||||
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), 4711))
|
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), 4711))
|
||||||
.andDo(MockMvcResultPrinter.print())
|
.andDo(MockMvcResultPrinter.print())
|
||||||
@@ -363,7 +366,7 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
|||||||
final String knownControllerId1 = "0815";
|
final String knownControllerId1 = "0815";
|
||||||
final long create = System.currentTimeMillis();
|
final long create = System.currentTimeMillis();
|
||||||
// make a poll, audit information should be set on plug and play
|
// make a poll, audit information should be set on plug and play
|
||||||
SecurityContextSwitch.runAs(SecurityContextSwitch.withController("controller", CONTROLLER_ROLE_ANONYMOUS),
|
callAs(withController("controller", CONTROLLER_ROLE_ANONYMOUS),
|
||||||
() -> {
|
() -> {
|
||||||
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), knownControllerId1))
|
mvc.perform(get(CONTROLLER_BASE, tenantAware.getCurrentTenant(), knownControllerId1))
|
||||||
.andDo(MockMvcResultPrinter.print())
|
.andDo(MockMvcResultPrinter.print())
|
||||||
@@ -582,7 +585,7 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
|||||||
void sleepTimeResponseForDifferentMaintenanceWindowParameters() throws Exception {
|
void sleepTimeResponseForDifferentMaintenanceWindowParameters() throws Exception {
|
||||||
final DistributionSet ds = testdataFactory.createDistributionSet("");
|
final DistributionSet ds = testdataFactory.createDistributionSet("");
|
||||||
|
|
||||||
SecurityContextSwitch.runAs(SecurityContextSwitch.withUser("tenantadmin", TENANT_CONFIGURATION),
|
getAs(withUser("tenantadmin", TENANT_CONFIGURATION),
|
||||||
() -> {
|
() -> {
|
||||||
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.POLLING_TIME, "00:05:00");
|
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.POLLING_TIME, "00:05:00");
|
||||||
return null;
|
return null;
|
||||||
@@ -751,7 +754,7 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void withPollingTime(final String pollingTime, final Callable<Void> runnable) throws Exception {
|
private void withPollingTime(final String pollingTime, final Callable<Void> runnable) throws Exception {
|
||||||
SecurityContextSwitch.runAs(SecurityContextSwitch.withUser("tenantadmin", TENANT_CONFIGURATION),
|
getAs(withUser("tenantadmin", TENANT_CONFIGURATION),
|
||||||
() -> {
|
() -> {
|
||||||
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.POLLING_TIME, pollingTime);
|
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.POLLING_TIME, pollingTime);
|
||||||
return null;
|
return null;
|
||||||
@@ -759,7 +762,7 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
|
|||||||
try {
|
try {
|
||||||
runnable.call();
|
runnable.call();
|
||||||
} finally {
|
} finally {
|
||||||
SecurityContextSwitch.runAs(SecurityContextSwitch.withUser("tenantadmin", TENANT_CONFIGURATION),
|
getAs(withUser("tenantadmin", TENANT_CONFIGURATION),
|
||||||
() -> {
|
() -> {
|
||||||
tenantConfigurationManagement.deleteConfiguration(TenantConfigurationKey.POLLING_TIME);
|
tenantConfigurationManagement.deleteConfiguration(TenantConfigurationKey.POLLING_TIME);
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -8,6 +8,10 @@
|
|||||||
# SPDX-License-Identifier: EPL-2.0
|
# SPDX-License-Identifier: EPL-2.0
|
||||||
#
|
#
|
||||||
|
|
||||||
|
# Logging START - activate to see request/response details
|
||||||
|
#logging.level.org.eclipse.hawkbit.rest.util.MockMvcResultPrinter=DEBUG
|
||||||
|
# Logging END
|
||||||
|
|
||||||
# DDI configuration - START
|
# DDI configuration - START
|
||||||
hawkbit.controller.pollingTime=00:01:00
|
hawkbit.controller.pollingTime=00:01:00
|
||||||
hawkbit.controller.pollingOverdueTime=00:01:00
|
hawkbit.controller.pollingOverdueTime=00:01:00
|
||||||
@@ -21,6 +25,6 @@ spring.servlet.multipart.max-file-size=5MB
|
|||||||
hawkbit.server.security.dos.maxStatusEntriesPerAction=100
|
hawkbit.server.security.dos.maxStatusEntriesPerAction=100
|
||||||
hawkbit.server.security.dos.maxAttributeEntriesPerTarget=10
|
hawkbit.server.security.dos.maxAttributeEntriesPerTarget=10
|
||||||
# Quota - END
|
# Quota - END
|
||||||
# Logging START - activate to see request/response details
|
|
||||||
#logging.level.org.eclipse.hawkbit.rest.util.MockMvcResultPrinter=DEBUG
|
# disable spring cloud bus for tests
|
||||||
# Logging END
|
spring.cloud.bus.enabled=false
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import org.eclipse.hawkbit.repository.event.remote.CancelTargetAssignmentEvent;
|
|||||||
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
|
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
|
||||||
import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent;
|
import org.eclipse.hawkbit.repository.event.remote.TargetAttributesRequestedEvent;
|
||||||
import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent;
|
import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent;
|
||||||
import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration;
|
import org.eclipse.hawkbit.repository.jpa.JpaRepositoryConfiguration;
|
||||||
import org.eclipse.hawkbit.repository.model.Action;
|
import org.eclipse.hawkbit.repository.model.Action;
|
||||||
import org.eclipse.hawkbit.repository.model.Artifact;
|
import org.eclipse.hawkbit.repository.model.Artifact;
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||||
@@ -60,13 +60,17 @@ import org.springframework.amqp.support.converter.AbstractJavaTypeMapper;
|
|||||||
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
import org.springframework.test.context.ActiveProfiles;
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
import org.springframework.test.context.TestPropertySource;
|
||||||
|
|
||||||
@ActiveProfiles({ "test" })
|
|
||||||
/**
|
/**
|
||||||
* Feature: Component Tests - Device Management Federation API<br/>
|
* Feature: Component Tests - Device Management Federation API<br/>
|
||||||
* Story: AmqpMessage Dispatcher Service Test
|
* Story: AmqpMessage Dispatcher Service Test
|
||||||
*/
|
*/
|
||||||
@SpringBootTest(classes = { RepositoryApplicationConfiguration.class }, webEnvironment = SpringBootTest.WebEnvironment.NONE)
|
@ActiveProfiles({ "test" })
|
||||||
|
@SpringBootTest(classes = { JpaRepositoryConfiguration.class }, webEnvironment = SpringBootTest.WebEnvironment.NONE)
|
||||||
|
@TestPropertySource(properties = {
|
||||||
|
"spring.main.allow-bean-definition-overriding=true",
|
||||||
|
"spring.cloud.bus.enabled=true" })
|
||||||
class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
|
class AmqpMessageDispatcherServiceTest extends AbstractIntegrationTest {
|
||||||
|
|
||||||
private static final String TENANT = "DEFAULT";
|
private static final String TENANT = "DEFAULT";
|
||||||
|
|||||||
@@ -71,15 +71,14 @@ import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
|||||||
import org.springframework.amqp.support.converter.MessageConversionException;
|
import org.springframework.amqp.support.converter.MessageConversionException;
|
||||||
import org.springframework.amqp.support.converter.MessageConverter;
|
import org.springframework.amqp.support.converter.MessageConverter;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
|
||||||
/**
|
/**
|
||||||
* Feature: Component Tests - Device Management Federation API<br/>
|
* Feature: Component Tests - Device Management Federation API<br/>
|
||||||
* Story: AmqpMessage Handler Service Test
|
* Story: AmqpMessage Handler Service Test
|
||||||
*/
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
class AmqpMessageHandlerServiceTest {
|
class AmqpMessageHandlerServiceTest {
|
||||||
|
|
||||||
private static final String FAIL_MESSAGE_AMQP_REJECT_REASON = AmqpRejectAndDontRequeueException.class
|
private static final String FAIL_MESSAGE_AMQP_REJECT_REASON = AmqpRejectAndDontRequeueException.class.getSimpleName() + " was expected, ";
|
||||||
.getSimpleName() + " was expected, ";
|
|
||||||
|
|
||||||
private static final String VIRTUAL_HOST = "vHost";
|
private static final String VIRTUAL_HOST = "vHost";
|
||||||
private static final String TENANT = "DEFAULT";
|
private static final String TENANT = "DEFAULT";
|
||||||
|
|||||||
@@ -31,11 +31,11 @@ import org.springframework.amqp.rabbit.core.RabbitTemplate;
|
|||||||
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
|
||||||
import org.springframework.amqp.support.converter.MessageConversionException;
|
import org.springframework.amqp.support.converter.MessageConversionException;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
|
||||||
/**
|
/**
|
||||||
* Feature: Component Tests - Device Management Federation API<br/>
|
* Feature: Component Tests - Device Management Federation API<br/>
|
||||||
* Story: Base Amqp Service Test
|
* Story: Base Amqp Service Test
|
||||||
*/
|
*/
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
class BaseAmqpServiceTest {
|
class BaseAmqpServiceTest {
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ import org.eclipse.hawkbit.integration.listener.ReplyToListener;
|
|||||||
import org.eclipse.hawkbit.matcher.SoftwareModuleJsonMatcher;
|
import org.eclipse.hawkbit.matcher.SoftwareModuleJsonMatcher;
|
||||||
import org.eclipse.hawkbit.rabbitmq.test.AbstractAmqpIntegrationTest;
|
import org.eclipse.hawkbit.rabbitmq.test.AbstractAmqpIntegrationTest;
|
||||||
import org.eclipse.hawkbit.rabbitmq.test.AmqpTestConfiguration;
|
import org.eclipse.hawkbit.rabbitmq.test.AmqpTestConfiguration;
|
||||||
import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration;
|
import org.eclipse.hawkbit.repository.jpa.JpaRepositoryConfiguration;
|
||||||
import org.eclipse.hawkbit.repository.model.Action;
|
import org.eclipse.hawkbit.repository.model.Action;
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
|
import org.eclipse.hawkbit.repository.model.DistributionSetAssignmentResult;
|
||||||
@@ -66,7 +66,7 @@ import org.springframework.util.CollectionUtils;
|
|||||||
*/
|
*/
|
||||||
@ContextConfiguration(classes = {
|
@ContextConfiguration(classes = {
|
||||||
DmfApiConfiguration.class, DmfTestConfiguration.class,
|
DmfApiConfiguration.class, DmfTestConfiguration.class,
|
||||||
RepositoryApplicationConfiguration.class, AmqpTestConfiguration.class })
|
JpaRepositoryConfiguration.class, AmqpTestConfiguration.class })
|
||||||
abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpIntegrationTest {
|
abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpIntegrationTest {
|
||||||
|
|
||||||
protected static final String TENANT_EXIST = "DEFAULT";
|
protected static final String TENANT_EXIST = "DEFAULT";
|
||||||
@@ -93,9 +93,9 @@ abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpIntegratio
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected <T> T waitUntilIsPresent(final Callable<Optional<T>> callable) {
|
protected <T> T waitUntilIsPresent(final Callable<Optional<T>> callable) {
|
||||||
await().until(() -> SecurityContextSwitch.runAsPrivileged(() -> callable.call().isPresent()));
|
await().until(() -> SecurityContextSwitch.callAsPrivileged(() -> callable.call().isPresent()));
|
||||||
try {
|
try {
|
||||||
return SecurityContextSwitch.runAsPrivileged(() -> callable.call().get());
|
return SecurityContextSwitch.callAsPrivileged(() -> callable.call().get());
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -367,7 +367,7 @@ abstract class AbstractAmqpServiceIntegrationTest extends AbstractAmqpIntegratio
|
|||||||
waitUntilIsPresent(() -> controllerManagement.getByControllerId(controllerId));
|
waitUntilIsPresent(() -> controllerManagement.getByControllerId(controllerId));
|
||||||
await().untilAsserted(() -> {
|
await().untilAsserted(() -> {
|
||||||
try {
|
try {
|
||||||
final Map<String, String> controllerAttributes = SecurityContextSwitch.runAsPrivileged(
|
final Map<String, String> controllerAttributes = SecurityContextSwitch.callAsPrivileged(
|
||||||
() -> targetManagement.getControllerAttributes(controllerId));
|
() -> targetManagement.getControllerAttributes(controllerId));
|
||||||
assertThat(controllerAttributes).hasSameSizeAs(attributes);
|
assertThat(controllerAttributes).hasSameSizeAs(attributes);
|
||||||
assertThat(controllerAttributes).containsAllEntriesOf(attributes);
|
assertThat(controllerAttributes).containsAllEntriesOf(attributes);
|
||||||
|
|||||||
@@ -794,7 +794,7 @@ class AmqpMessageDispatcherServiceIntegrationTest extends AbstractAmqpServiceInt
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void waitUntil(final Callable<Boolean> callable) {
|
private void waitUntil(final Callable<Boolean> callable) {
|
||||||
await().until(() -> SecurityContextSwitch.runAsPrivileged(callable));
|
await().until(() -> SecurityContextSwitch.callAsPrivileged(callable));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertLatestMultiActionMessageContainsInstallMessages(final String controllerId,
|
private void assertLatestMultiActionMessageContainsInstallMessages(final String controllerId,
|
||||||
|
|||||||
@@ -1231,7 +1231,7 @@ class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServiceIntegr
|
|||||||
private void assertAction(final Long actionId, final int messages, final Status... expectedActionStates) {
|
private void assertAction(final Long actionId, final int messages, final Status... expectedActionStates) {
|
||||||
await().untilAsserted(() -> {
|
await().untilAsserted(() -> {
|
||||||
try {
|
try {
|
||||||
SecurityContextSwitch.runAsPrivileged(() -> {
|
SecurityContextSwitch.callAsPrivileged(() -> {
|
||||||
final List<ActionStatus> actionStatusList = deploymentManagement.findActionStatusByAction(actionId, PAGE).getContent();
|
final List<ActionStatus> actionStatusList = deploymentManagement.findActionStatusByAction(actionId, PAGE).getContent();
|
||||||
|
|
||||||
// Check correlation ID
|
// Check correlation ID
|
||||||
@@ -1265,7 +1265,7 @@ class AmqpMessageHandlerServiceIntegrationTest extends AbstractAmqpServiceIntegr
|
|||||||
final Status... expectedActionStates) {
|
final Status... expectedActionStates) {
|
||||||
await().untilAsserted(() -> {
|
await().untilAsserted(() -> {
|
||||||
try {
|
try {
|
||||||
SecurityContextSwitch.runAsPrivileged(() -> {
|
SecurityContextSwitch.callAsPrivileged(() -> {
|
||||||
final List<ActionStatus> actionStatusList = deploymentManagement
|
final List<ActionStatus> actionStatusList = deploymentManagement
|
||||||
.findActionStatusByAction(actionId, PAGE).getContent();
|
.findActionStatusByAction(actionId, PAGE).getContent();
|
||||||
assertThat(actionStatusList).hasSize(statusListCount);
|
assertThat(actionStatusList).hasSize(statusListCount);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.awaitility.Awaitility;
|
import org.awaitility.Awaitility;
|
||||||
import org.awaitility.core.ConditionFactory;
|
import org.awaitility.core.ConditionFactory;
|
||||||
import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration;
|
import org.eclipse.hawkbit.repository.jpa.JpaRepositoryConfiguration;
|
||||||
import org.eclipse.hawkbit.repository.test.TestConfiguration;
|
import org.eclipse.hawkbit.repository.test.TestConfiguration;
|
||||||
import org.eclipse.hawkbit.repository.test.util.AbstractIntegrationTest;
|
import org.eclipse.hawkbit.repository.test.util.AbstractIntegrationTest;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
@@ -30,12 +30,16 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.test.annotation.DirtiesContext;
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
import org.springframework.test.annotation.DirtiesContext.ClassMode;
|
import org.springframework.test.annotation.DirtiesContext.ClassMode;
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.TestPropertySource;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RabbitAvailable
|
@RabbitAvailable
|
||||||
@ContextConfiguration(classes = { RepositoryApplicationConfiguration.class, AmqpTestConfiguration.class, TestConfiguration.class })
|
@ContextConfiguration(classes = { JpaRepositoryConfiguration.class, AmqpTestConfiguration.class, TestConfiguration.class })
|
||||||
// Dirty context is necessary to create a new vhost and recreate all necessary beans after every test class.
|
// Dirty context is necessary to create a new vhost and recreate all necessary beans after every test class.
|
||||||
@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
|
@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
|
||||||
|
@TestPropertySource(properties = {
|
||||||
|
"spring.main.allow-bean-definition-overriding=true",
|
||||||
|
"spring.cloud.bus.enabled=true" })
|
||||||
@SuppressWarnings("java:S6813") // constructor injects are not possible for test classes
|
@SuppressWarnings("java:S6813") // constructor injects are not possible for test classes
|
||||||
public abstract class AbstractAmqpIntegrationTest extends AbstractIntegrationTest {
|
public abstract class AbstractAmqpIntegrationTest extends AbstractIntegrationTest {
|
||||||
|
|
||||||
@@ -60,6 +64,7 @@ public abstract class AbstractAmqpIntegrationTest extends AbstractIntegrationTes
|
|||||||
private static final Duration AT_LEAST = Duration.ofMillis(Integer.getInteger("hawkbit.it.amqp.await.atLeastMs", 100));
|
private static final Duration AT_LEAST = Duration.ofMillis(Integer.getInteger("hawkbit.it.amqp.await.atLeastMs", 100));
|
||||||
private static final Duration POLL_INTERVAL = Duration.ofMillis(Integer.getInteger("hawkbit.it.amqp.await.pollIntervalMs", 200));
|
private static final Duration POLL_INTERVAL = Duration.ofMillis(Integer.getInteger("hawkbit.it.amqp.await.pollIntervalMs", 200));
|
||||||
private static final Duration TIMEOUT = Duration.ofMillis(Integer.getInteger("hawkbit.it.amqp.await.timeoutMs", 5000));
|
private static final Duration TIMEOUT = Duration.ofMillis(Integer.getInteger("hawkbit.it.amqp.await.timeoutMs", 5000));
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ConditionFactory await() {
|
protected ConditionFactory await() {
|
||||||
return Awaitility.await().atLeast(AT_LEAST).pollInterval(POLL_INTERVAL).atMost(TIMEOUT);
|
return Awaitility.await().atLeast(AT_LEAST).pollInterval(POLL_INTERVAL).atMost(TIMEOUT);
|
||||||
|
|||||||
@@ -19,13 +19,12 @@ import org.springframework.security.config.annotation.method.configuration.Enabl
|
|||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable {@link ComponentScan} in the resource package to setup all
|
* Enable {@link ComponentScan} in the resource package to set up all {@link Controller} annotated classes and set up the REST-Resources
|
||||||
* {@link Controller} annotated classes and setup the REST-Resources for the
|
* for the Management API.
|
||||||
* Management API.
|
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableMethodSecurity(proxyTargetClass = true, securedEnabled = true)
|
@EnableMethodSecurity(proxyTargetClass = true, securedEnabled = true)
|
||||||
@ComponentScan
|
@ComponentScan
|
||||||
@Import({ RestConfiguration.class, OpenApi.class })
|
@Import({ RestConfiguration.class, OpenApi.class })
|
||||||
@PropertySource("classpath:/hawkbit-mgmt-api-defaults.properties")
|
@PropertySource("classpath:/hawkbit-mgmt-api-defaults.properties")
|
||||||
public class MgmtApiConfiguration {}
|
public class MgmtApiConfiguration {}
|
||||||
@@ -15,7 +15,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||||||
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
|
import org.eclipse.hawkbit.mgmt.json.model.distributionset.MgmtActionType;
|
||||||
import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration;
|
import org.eclipse.hawkbit.repository.jpa.JpaRepositoryConfiguration;
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
|
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
|
||||||
import org.eclipse.hawkbit.repository.model.BaseEntity;
|
import org.eclipse.hawkbit.repository.model.BaseEntity;
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||||
@@ -32,7 +32,7 @@ import org.springframework.test.context.TestPropertySource;
|
|||||||
import org.springframework.test.web.servlet.ResultMatcher;
|
import org.springframework.test.web.servlet.ResultMatcher;
|
||||||
|
|
||||||
@ContextConfiguration(
|
@ContextConfiguration(
|
||||||
classes = { MgmtApiConfiguration.class, RestConfiguration.class, RepositoryApplicationConfiguration.class, TestConfiguration.class })
|
classes = { MgmtApiConfiguration.class, RestConfiguration.class, JpaRepositoryConfiguration.class, TestConfiguration.class })
|
||||||
@TestPropertySource(locations = "classpath:/mgmt-test.properties")
|
@TestPropertySource(locations = "classpath:/mgmt-test.properties")
|
||||||
public abstract class AbstractManagementApiIntegrationTest extends AbstractRestIntegrationTest {
|
public abstract class AbstractManagementApiIntegrationTest extends AbstractRestIntegrationTest {
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
|
||||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
|
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
|
||||||
import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration;
|
import org.eclipse.hawkbit.repository.jpa.JpaRepositoryConfiguration;
|
||||||
import org.eclipse.hawkbit.repository.test.TestConfiguration;
|
import org.eclipse.hawkbit.repository.test.TestConfiguration;
|
||||||
import org.eclipse.hawkbit.repository.test.matcher.EventVerifier;
|
import org.eclipse.hawkbit.repository.test.matcher.EventVerifier;
|
||||||
import org.eclipse.hawkbit.repository.test.util.CleanupTestExecutionListener;
|
import org.eclipse.hawkbit.repository.test.util.CleanupTestExecutionListener;
|
||||||
@@ -67,7 +67,7 @@ import org.springframework.web.context.WebApplicationContext;
|
|||||||
@WebAppConfiguration
|
@WebAppConfiguration
|
||||||
@AutoConfigureMockMvc
|
@AutoConfigureMockMvc
|
||||||
@ContextConfiguration(classes = { MgmtApiConfiguration.class, RestConfiguration.class,
|
@ContextConfiguration(classes = { MgmtApiConfiguration.class, RestConfiguration.class,
|
||||||
RepositoryApplicationConfiguration.class, TestConfiguration.class })
|
JpaRepositoryConfiguration.class, TestConfiguration.class })
|
||||||
/**
|
/**
|
||||||
* Feature: Component Tests - Management API<br/>
|
* Feature: Component Tests - Management API<br/>
|
||||||
* Story: Basic auth Userinfo Resource
|
* Story: Basic auth Userinfo Resource
|
||||||
|
|||||||
@@ -408,7 +408,7 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe
|
|||||||
try {
|
try {
|
||||||
payload.put(new JSONObject().put("id", trg.getId()));
|
payload.put(new JSONObject().put("id", trg.getId()));
|
||||||
} catch (final JSONException e) {
|
} catch (final JSONException e) {
|
||||||
e.printStackTrace();
|
throw new IllegalStateException(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -459,7 +459,7 @@ class MgmtDistributionSetResourceTest extends AbstractManagementApiIntegrationTe
|
|||||||
try {
|
try {
|
||||||
list.put(new JSONObject().put("id", target.getControllerId()));
|
list.put(new JSONObject().put("id", target.getControllerId()));
|
||||||
} catch (final JSONException e) {
|
} catch (final JSONException e) {
|
||||||
e.printStackTrace();
|
throw new IllegalStateException(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1912,13 +1912,13 @@ class MgmtRolloutResourceTest extends AbstractManagementApiIntegrationTest {
|
|||||||
|
|
||||||
private void awaitRunningState(final Long rolloutId) {
|
private void awaitRunningState(final Long rolloutId) {
|
||||||
awaitRollout().until(() -> SecurityContextSwitch
|
awaitRollout().until(() -> SecurityContextSwitch
|
||||||
.runAsPrivileged(() -> rolloutManagement.get(rolloutId).orElseThrow(NoSuchElementException::new))
|
.callAsPrivileged(() -> rolloutManagement.get(rolloutId).orElseThrow(NoSuchElementException::new))
|
||||||
.getStatus().equals(RolloutStatus.RUNNING));
|
.getStatus().equals(RolloutStatus.RUNNING));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void awaitActionStatus(final Long actionId, final Status status) {
|
private void awaitActionStatus(final Long actionId, final Status status) {
|
||||||
awaitRollout().until(() -> SecurityContextSwitch
|
awaitRollout().until(() -> SecurityContextSwitch
|
||||||
.runAsPrivileged(() -> deploymentManagement.findAction(actionId).orElseThrow(NoSuchElementException::new))
|
.callAsPrivileged(() -> deploymentManagement.findAction(actionId).orElseThrow(NoSuchElementException::new))
|
||||||
.getStatus().equals(status));
|
.getStatus().equals(status));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.mgmt.rest.resource;
|
package org.eclipse.hawkbit.mgmt.rest.resource;
|
||||||
|
|
||||||
|
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.callAs;
|
||||||
|
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.getAs;
|
||||||
|
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.withUser;
|
||||||
import static org.hamcrest.CoreMatchers.equalTo;
|
import static org.hamcrest.CoreMatchers.equalTo;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
|
||||||
@@ -22,7 +25,6 @@ import org.eclipse.hawkbit.im.authentication.SpPermission;
|
|||||||
import org.eclipse.hawkbit.mgmt.json.model.system.MgmtSystemTenantConfigurationValueRequest;
|
import org.eclipse.hawkbit.mgmt.json.model.system.MgmtSystemTenantConfigurationValueRequest;
|
||||||
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
|
import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants;
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSetType;
|
import org.eclipse.hawkbit.repository.model.DistributionSetType;
|
||||||
import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch;
|
|
||||||
import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter;
|
import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter;
|
||||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
|
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
@@ -132,7 +134,7 @@ public class MgmtTenantManagementResourceTest extends AbstractManagementApiInteg
|
|||||||
* Update DefaultDistributionSetType Fails if given DistributionSetType ID does not exist.
|
* Update DefaultDistributionSetType Fails if given DistributionSetType ID does not exist.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void putTenantMetadataFails() throws Exception {
|
void putTenantMetadataFails() throws Exception {
|
||||||
long oldDefaultDsType = getActualDefaultDsType();
|
long oldDefaultDsType = getActualDefaultDsType();
|
||||||
//try an invalid input
|
//try an invalid input
|
||||||
String newDefaultDsType = new JSONObject().put("value", true).toString();
|
String newDefaultDsType = new JSONObject().put("value", true).toString();
|
||||||
@@ -149,7 +151,7 @@ public class MgmtTenantManagementResourceTest extends AbstractManagementApiInteg
|
|||||||
* The 'multi.assignments.enabled' property must not be changed to false.
|
* The 'multi.assignments.enabled' property must not be changed to false.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void deactivateMultiAssignment() throws Exception {
|
void deactivateMultiAssignment() throws Exception {
|
||||||
final String bodyActivate = new JSONObject().put("value", true).toString();
|
final String bodyActivate = new JSONObject().put("value", true).toString();
|
||||||
final String bodyDeactivate = new JSONObject().put("value", false).toString();
|
final String bodyDeactivate = new JSONObject().put("value", false).toString();
|
||||||
|
|
||||||
@@ -168,7 +170,7 @@ public class MgmtTenantManagementResourceTest extends AbstractManagementApiInteg
|
|||||||
* The Batch configuration should not be applied, because of invalid TenantConfiguration props
|
* The Batch configuration should not be applied, because of invalid TenantConfiguration props
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void changeBatchConfigurationShouldFailOnInvalidTenantConfiguration() throws Exception {
|
void changeBatchConfigurationShouldFailOnInvalidTenantConfiguration() throws Exception {
|
||||||
//in this scenario
|
//in this scenario
|
||||||
// some TenantConfiguration are not valid,
|
// some TenantConfiguration are not valid,
|
||||||
// TenantMetadata - DefaultDSType ID is valid,
|
// TenantMetadata - DefaultDSType ID is valid,
|
||||||
@@ -185,7 +187,7 @@ public class MgmtTenantManagementResourceTest extends AbstractManagementApiInteg
|
|||||||
* The Batch configuration should not be applied, because of invalid TenantMetadata (DefaultDistributionSetType)
|
* The Batch configuration should not be applied, because of invalid TenantMetadata (DefaultDistributionSetType)
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void changeBatchConfigurationShouldOnInvalidTenantMetadata() throws Exception {
|
void changeBatchConfigurationShouldOnInvalidTenantMetadata() throws Exception {
|
||||||
//in this scenario
|
//in this scenario
|
||||||
// all TenantConfiguration have valid and new values - using old values, inverted
|
// all TenantConfiguration have valid and new values - using old values, inverted
|
||||||
// TenantMetadata - DefaultDSType ID is invalid
|
// TenantMetadata - DefaultDSType ID is invalid
|
||||||
@@ -218,7 +220,7 @@ public class MgmtTenantManagementResourceTest extends AbstractManagementApiInteg
|
|||||||
* The Batch configuration should be applied
|
* The Batch configuration should be applied
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void changeBatchConfiguration() throws Exception {
|
void changeBatchConfiguration() throws Exception {
|
||||||
long updatedDistributionSetType = createTestDistributionSetType();
|
long updatedDistributionSetType = createTestDistributionSetType();
|
||||||
boolean updatedRolloutApprovalEnabled = true;
|
boolean updatedRolloutApprovalEnabled = true;
|
||||||
boolean updatedAuthGatewayTokenEnabled = true;
|
boolean updatedAuthGatewayTokenEnabled = true;
|
||||||
@@ -253,7 +255,7 @@ public class MgmtTenantManagementResourceTest extends AbstractManagementApiInteg
|
|||||||
* The 'repository.actions.autoclose.enabled' property must not be modified if Multi-Assignments is enabled.
|
* The 'repository.actions.autoclose.enabled' property must not be modified if Multi-Assignments is enabled.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void autoCloseCannotBeModifiedIfMultiAssignmentIsEnabled() throws Exception {
|
void autoCloseCannotBeModifiedIfMultiAssignmentIsEnabled() throws Exception {
|
||||||
final String bodyActivate = new JSONObject().put("value", true).toString();
|
final String bodyActivate = new JSONObject().put("value", true).toString();
|
||||||
final String bodyDeactivate = new JSONObject().put("value", false).toString();
|
final String bodyDeactivate = new JSONObject().put("value", false).toString();
|
||||||
|
|
||||||
@@ -280,7 +282,7 @@ public class MgmtTenantManagementResourceTest extends AbstractManagementApiInteg
|
|||||||
* Handles DELETE request deleting a tenant specific configuration.
|
* Handles DELETE request deleting a tenant specific configuration.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void deleteTenantConfiguration() throws Exception {
|
void deleteTenantConfiguration() throws Exception {
|
||||||
mvc.perform(delete(MgmtRestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs/{keyName}",
|
mvc.perform(delete(MgmtRestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs/{keyName}",
|
||||||
TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY))
|
TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY))
|
||||||
.andDo(MockMvcResultPrinter.print())
|
.andDo(MockMvcResultPrinter.print())
|
||||||
@@ -291,7 +293,7 @@ public class MgmtTenantManagementResourceTest extends AbstractManagementApiInteg
|
|||||||
* Tests DELETE request must Fail for TenantMetadata properties.
|
* Tests DELETE request must Fail for TenantMetadata properties.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void deleteTenantMetadataFail() throws Exception {
|
void deleteTenantMetadataFail() throws Exception {
|
||||||
mvc.perform(delete(MgmtRestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs/{keyName}",
|
mvc.perform(delete(MgmtRestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs/{keyName}",
|
||||||
DEFAULT_DISTRIBUTION_SET_TYPE_KEY))
|
DEFAULT_DISTRIBUTION_SET_TYPE_KEY))
|
||||||
.andDo(MockMvcResultPrinter.print())
|
.andDo(MockMvcResultPrinter.print())
|
||||||
@@ -303,18 +305,15 @@ public class MgmtTenantManagementResourceTest extends AbstractManagementApiInteg
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void getTenantConfigurationReadGWToken() throws Exception {
|
void getTenantConfigurationReadGWToken() throws Exception {
|
||||||
SecurityContextSwitch.runAs(SecurityContextSwitch.withUser("tenant_admin", SpPermission.TENANT_CONFIGURATION), () -> {
|
getAs(withUser("tenant_admin", SpPermission.TENANT_CONFIGURATION), () -> {
|
||||||
tenantConfigurationManagement.addOrUpdateConfiguration(
|
tenantConfigurationManagement.addOrUpdateConfiguration(
|
||||||
TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY,
|
TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY, "123");
|
||||||
"123");
|
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO - should be able to read with TENANT_CONFIGURATION but somehow here the role hierarchy doesn't play
|
// TODO - should be able to read with TENANT_CONFIGURATION but somehow here the role hierarchy doesn't play
|
||||||
// checked in mgmt / update server runtime PreAuthorizeEnabledTest
|
// checked in mgmt / update server runtime PreAuthorizeEnabledTest
|
||||||
SecurityContextSwitch.runAs(
|
callAs(withUser("tenant_admin", SpPermission.READ_TENANT_CONFIGURATION, SpPermission.READ_GATEWAY_SEC_TOKEN), () -> {
|
||||||
SecurityContextSwitch.withUser("tenant_admin", SpPermission.READ_TENANT_CONFIGURATION, SpPermission.READ_GATEWAY_SEC_TOKEN),
|
|
||||||
() -> {
|
|
||||||
mvc.perform(get(MgmtRestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs"))
|
mvc.perform(get(MgmtRestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs"))
|
||||||
.andDo(MockMvcResultPrinter.print())
|
.andDo(MockMvcResultPrinter.print())
|
||||||
.andDo(m -> System.out.println("-> 1: " + m.getResponse().getContentAsString()))
|
.andDo(m -> System.out.println("-> 1: " + m.getResponse().getContentAsString()))
|
||||||
@@ -324,7 +323,7 @@ public class MgmtTenantManagementResourceTest extends AbstractManagementApiInteg
|
|||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
SecurityContextSwitch.runAs(SecurityContextSwitch.withUser("tenant_read", SpPermission.READ_TENANT_CONFIGURATION), () -> {
|
callAs(withUser("tenant_read", SpPermission.READ_TENANT_CONFIGURATION), () -> {
|
||||||
mvc.perform(get(MgmtRestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs"))
|
mvc.perform(get(MgmtRestConstants.SYSTEM_V1_REQUEST_MAPPING + "/configs"))
|
||||||
.andDo(MockMvcResultPrinter.print())
|
.andDo(MockMvcResultPrinter.print())
|
||||||
.andDo(m -> System.out.println("-> 2: " + m.getResponse().getContentAsString()))
|
.andDo(m -> System.out.println("-> 2: " + m.getResponse().getContentAsString()))
|
||||||
|
|||||||
@@ -12,3 +12,5 @@
|
|||||||
#logging.level.org.eclipse.hawkbit.rest.util.MockMvcResultPrinter=DEBUG
|
#logging.level.org.eclipse.hawkbit.rest.util.MockMvcResultPrinter=DEBUG
|
||||||
# Logging END
|
# Logging END
|
||||||
|
|
||||||
|
# disable spring cloud bus for tests
|
||||||
|
spring.cloud.bus.enabled=false
|
||||||
@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.app.mgmt;
|
|||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
@@ -47,6 +48,32 @@ class PreAuthorizeEnabledTest extends AbstractSecurityTest {
|
|||||||
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value()));
|
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests whether request returns distribution set if a role with scope is granted for the user
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@WithUser(authorities = { SpPermission.CREATE_REPOSITORY, SpPermission.READ_REPOSITORY + "/name==DsOne" }, autoCreateTenant = false)
|
||||||
|
void successIfHasRoleWithScope() throws Exception {
|
||||||
|
createDsOne("successIfHasRoleWithScope");
|
||||||
|
mvc.perform(get("/rest/v1/distributionsets")).andExpect(result -> {
|
||||||
|
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value());
|
||||||
|
assertThat(result.getResponse().getContentAsString()).contains("DsOne");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests whether request doesn't return distribution set if a role with scope doesn't grant access
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@WithUser(authorities = { SpPermission.CREATE_REPOSITORY, SpPermission.READ_REPOSITORY + "/name==DsOne2" }, autoCreateTenant = false)
|
||||||
|
void failIfHasNoForbiddingScope() throws Exception {
|
||||||
|
createDsOne("failIfHasNoForbiddingScope");
|
||||||
|
mvc.perform(get("/rest/v1/distributionsets")).andExpect(result -> {
|
||||||
|
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value());
|
||||||
|
assertThat(result.getResponse().getContentAsString()).doesNotContain("DsOne");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests whether request succeed if a role is granted for the user
|
* Tests whether request succeed if a role is granted for the user
|
||||||
*/
|
*/
|
||||||
@@ -80,4 +107,17 @@ class PreAuthorizeEnabledTest extends AbstractSecurityTest {
|
|||||||
mvc.perform(get("/rest/v1/system/configs")).andExpect(result ->
|
mvc.perform(get("/rest/v1/system/configs")).andExpect(result ->
|
||||||
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value()));
|
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void createDsOne(final String version) throws Exception {
|
||||||
|
mvc.perform(post("/rest/v1/distributionsets")
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.content("""
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "DsOne",
|
||||||
|
"version": "${version}"
|
||||||
|
}
|
||||||
|
]""".replace("${version}", version)))
|
||||||
|
.andExpect(result -> assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.CREATED.value()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.app;
|
|||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
@@ -47,6 +48,32 @@ class PreAuthorizeEnabledTest extends AbstractSecurityTest {
|
|||||||
.andExpect(result -> assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value()));
|
.andExpect(result -> assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests whether request returns distribution set if a role with scope is granted for the user
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@WithUser(authorities = { SpPermission.CREATE_REPOSITORY, SpPermission.READ_REPOSITORY + "/name==DsOne" }, autoCreateTenant = false)
|
||||||
|
void successIfHasRoleWithScope() throws Exception {
|
||||||
|
createDsOne("successIfHasRoleWithScope");
|
||||||
|
mvc.perform(get("/rest/v1/distributionsets")).andExpect(result -> {
|
||||||
|
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value());
|
||||||
|
assertThat(result.getResponse().getContentAsString()).contains("DsOne");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests whether request doesn't return distribution set if a role with scope doesn't grant access
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
@WithUser(authorities = { SpPermission.CREATE_REPOSITORY, SpPermission.READ_REPOSITORY + "/name==DsOne2" }, autoCreateTenant = false)
|
||||||
|
void failIfHasNoForbiddingScope() throws Exception {
|
||||||
|
createDsOne("failIfHasNoForbiddingScope");
|
||||||
|
mvc.perform(get("/rest/v1/distributionsets")).andExpect(result -> {
|
||||||
|
assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value());
|
||||||
|
assertThat(result.getResponse().getContentAsString()).doesNotContain("DsOne");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests whether request succeed if a role is granted for the user
|
* Tests whether request succeed if a role is granted for the user
|
||||||
*/
|
*/
|
||||||
@@ -58,7 +85,7 @@ class PreAuthorizeEnabledTest extends AbstractSecurityTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests whether read tenant config request fail if a tenant config (or read read) is not granted for the user
|
* Tests whether read tenant config request fail if a tenant config (or read) is not granted for the user
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@WithUser(authorities = { SpPermission.READ_TARGET }, autoCreateTenant = false)
|
@WithUser(authorities = { SpPermission.READ_TARGET }, autoCreateTenant = false)
|
||||||
@@ -81,4 +108,17 @@ class PreAuthorizeEnabledTest extends AbstractSecurityTest {
|
|||||||
mvc.perform(get("/rest/v1/system/configs"))
|
mvc.perform(get("/rest/v1/system/configs"))
|
||||||
.andExpect(result -> assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value()));
|
.andExpect(result -> assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void createDsOne(final String version) throws Exception {
|
||||||
|
mvc.perform(post("/rest/v1/distributionsets")
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.content("""
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "DsOne",
|
||||||
|
"version": "${version}"
|
||||||
|
}
|
||||||
|
]""".replace("${version}", version)))
|
||||||
|
.andExpect(result -> assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.CREATED.value()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -33,10 +33,6 @@
|
|||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.hateoas</groupId>
|
|
||||||
<artifactId>spring-hateoas</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.cloud</groupId>
|
<groupId>org.springframework.cloud</groupId>
|
||||||
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
|
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
|
||||||
@@ -55,10 +51,6 @@
|
|||||||
<groupId>com.cronutils</groupId>
|
<groupId>com.cronutils</groupId>
|
||||||
<artifactId>cron-utils</artifactId>
|
<artifactId>cron-utils</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>cz.jirutka.rsql</groupId>
|
|
||||||
<artifactId>rsql-parser</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jsoup</groupId>
|
<groupId>org.jsoup</groupId>
|
||||||
<artifactId>jsoup</artifactId>
|
<artifactId>jsoup</artifactId>
|
||||||
|
|||||||
@@ -21,46 +21,35 @@ import com.cronutils.model.CronType;
|
|||||||
import com.cronutils.model.definition.CronDefinitionBuilder;
|
import com.cronutils.model.definition.CronDefinitionBuilder;
|
||||||
import com.cronutils.model.time.ExecutionTime;
|
import com.cronutils.model.time.ExecutionTime;
|
||||||
import com.cronutils.parser.CronParser;
|
import com.cronutils.parser.CronParser;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
import org.eclipse.hawkbit.repository.exception.InvalidMaintenanceScheduleException;
|
import org.eclipse.hawkbit.repository.exception.InvalidMaintenanceScheduleException;
|
||||||
import org.springframework.util.ObjectUtils;
|
import org.springframework.util.ObjectUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper class to check validity of maintenance schedule definition and manage
|
* Helper class to check validity of maintenance schedule definition and manage scheduling of maintenance window using
|
||||||
* scheduling of maintenance window using a cron expression based scheduler. It
|
* a cron expression based scheduler. It also provides a helper method for conversion of duration specified in HH:mm:ss format to ISO format.
|
||||||
* also provides a helper method for conversion of duration specified in
|
|
||||||
* HH:mm:ss format to ISO format.
|
|
||||||
*/
|
*/
|
||||||
|
@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
|
||||||
public final class MaintenanceScheduleHelper {
|
public final class MaintenanceScheduleHelper {
|
||||||
|
|
||||||
private static final CronParser cronParser = new CronParser(
|
private static final CronParser CRON_PARSER = new CronParser(CronDefinitionBuilder.instanceDefinitionFor(CronType.QUARTZ));
|
||||||
CronDefinitionBuilder.instanceDefinitionFor(CronType.QUARTZ));
|
|
||||||
|
|
||||||
private MaintenanceScheduleHelper() {
|
|
||||||
throw new IllegalStateException("Utility class");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the next available maintenance window.
|
* Calculate the next available maintenance window.
|
||||||
*
|
*
|
||||||
* @param cronSchedule is a cron expression with 6 mandatory fields and 1 last
|
* @param cronSchedule is a cron expression with 6 mandatory fields and 1 last optional field: "second minute hour day-of-month month
|
||||||
* optional field: "second minute hour dayofmonth month weekday
|
* weekday year".
|
||||||
* year".
|
* @param duration in HH:mm:ss format specifying the duration of a maintenance window, for example 00:30:00 for 30 minutes.
|
||||||
* @param duration in HH:mm:ss format specifying the duration of a maintenance
|
* @param timezone is the time zone specified as +/-hh:mm offset from UTC. For example +02:00 for CET summer time and +00:00 for UTC. The
|
||||||
* window, for example 00:30:00 for 30 minutes.
|
* start time of a maintenance window calculated based on the cron expression is relative to this time zone.
|
||||||
* @param timezone is the time zone specified as +/-hh:mm offset from UTC. For
|
* @return { @link Optional<ZonedDateTime>} of the next available window. In case there is none, or there are maintenance window validation
|
||||||
* example +02:00 for CET summer time and +00:00 for UTC. The
|
|
||||||
* start time of a maintenance window calculated based on the
|
|
||||||
* cron expression is relative to this time zone.
|
|
||||||
* @return { @link Optional<ZonedDateTime>} of the next available window. In
|
|
||||||
* case there is none, or there are maintenance window validation
|
|
||||||
* errors, returns empty value.
|
* errors, returns empty value.
|
||||||
*/
|
*/
|
||||||
// Exception squid:S1166 - if there are validation error(format of cron
|
// Exception squid:S1166 - if there are validation error(format of cron
|
||||||
// expression or duration is wrong), we simply return empty value
|
// expression or duration is wrong), we simply return empty value
|
||||||
@SuppressWarnings("squid:S1166")
|
@SuppressWarnings("squid:S1166")
|
||||||
public static Optional<ZonedDateTime> getNextMaintenanceWindow(final String cronSchedule, final String duration,
|
public static Optional<ZonedDateTime> getNextMaintenanceWindow(final String cronSchedule, final String duration, final String timezone) {
|
||||||
final String timezone) {
|
|
||||||
try {
|
try {
|
||||||
final ExecutionTime scheduleExecutor = ExecutionTime.forCron(getCronFromExpression(cronSchedule));
|
final ExecutionTime scheduleExecutor = ExecutionTime.forCron(getCronFromExpression(cronSchedule));
|
||||||
final ZonedDateTime now = ZonedDateTime.now(ZoneOffset.of(timezone));
|
final ZonedDateTime now = ZonedDateTime.now(ZoneOffset.of(timezone));
|
||||||
@@ -74,36 +63,28 @@ public final class MaintenanceScheduleHelper {
|
|||||||
/**
|
/**
|
||||||
* Parse the given cron expression with quartz parser.
|
* Parse the given cron expression with quartz parser.
|
||||||
*
|
*
|
||||||
* @param cronSchedule is a cron expression with 6 mandatory fields and 1 last
|
* @param cronSchedule is a cron expression with 6 mandatory fields and 1 last optional field: "second minute hour day-of-month
|
||||||
* optional field: "second minute hour dayofmonth month weekday
|
* month weekday year".
|
||||||
* year".
|
|
||||||
* @return {@link Cron} object, that corresponds to the expression.
|
* @return {@link Cron} object, that corresponds to the expression.
|
||||||
* @throws IllegalArgumentException if the cron expression doesn't have a valid format.
|
* @throws IllegalArgumentException if the cron expression doesn't have a valid format.
|
||||||
*/
|
*/
|
||||||
public static Cron getCronFromExpression(final String cronSchedule) {
|
public static Cron getCronFromExpression(final String cronSchedule) {
|
||||||
return cronParser.parse(cronSchedule);
|
return CRON_PARSER.parse(cronSchedule);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the maintenance schedule definition is valid in terms of
|
* Check if the maintenance schedule definition is valid in terms of validity of cron expression, duration and availability of at least one
|
||||||
* validity of cron expression, duration and availability of at least one
|
* valid maintenance window. Further a maintenance schedule is valid if either all the parameters: schedule, duration and time zone are
|
||||||
* valid maintenance window. Further a maintenance schedule is valid if
|
* valid or are null.
|
||||||
* either all the parameters: schedule, duration and time zone are valid or
|
|
||||||
* are null.
|
|
||||||
*
|
*
|
||||||
* @param cronSchedule is a cron expression with 6 mandatory fields and 1 last
|
* @param cronSchedule is a cron expression with 6 mandatory fields and 1 last optional field: "second minute hour day-of-month month
|
||||||
* optional field: "second minute hour dayofmonth month weekday
|
* weekday year".
|
||||||
* year".
|
* @param duration in HH:mm:ss format specifying the duration of a maintenance window, for example 00:30:00 for 30 minutes.
|
||||||
* @param duration in HH:mm:ss format specifying the duration of a maintenance
|
* @param timezone is the time zone specified as +/-hh:mm offset from UTC. For example +02:00 for CET summer time and +00:00 for UTC. The
|
||||||
* window, for example 00:30:00 for 30 minutes.
|
* start time of a maintenance window calculated based on the cron expression is relative to this time zone.
|
||||||
* @param timezone is the time zone specified as +/-hh:mm offset from UTC. For
|
|
||||||
* example +02:00 for CET summer time and +00:00 for UTC. The
|
|
||||||
* start time of a maintenance window calculated based on the
|
|
||||||
* cron expression is relative to this time zone.
|
|
||||||
* @throws InvalidMaintenanceScheduleException if the defined schedule fails the validity criteria.
|
* @throws InvalidMaintenanceScheduleException if the defined schedule fails the validity criteria.
|
||||||
*/
|
*/
|
||||||
public static void validateMaintenanceSchedule(final String cronSchedule, final String duration,
|
public static void validateMaintenanceSchedule(final String cronSchedule, final String duration, final String timezone) {
|
||||||
final String timezone) {
|
|
||||||
if (allNotEmpty(cronSchedule, duration, timezone)) {
|
if (allNotEmpty(cronSchedule, duration, timezone)) {
|
||||||
validateCronSchedule(cronSchedule);
|
validateCronSchedule(cronSchedule);
|
||||||
validateDuration(duration);
|
validateDuration(duration);
|
||||||
@@ -114,18 +95,15 @@ public final class MaintenanceScheduleHelper {
|
|||||||
"No valid maintenance window available after current time");
|
"No valid maintenance window available after current time");
|
||||||
}
|
}
|
||||||
} else if (atLeastOneNotEmpty(cronSchedule, duration, timezone)) {
|
} else if (atLeastOneNotEmpty(cronSchedule, duration, timezone)) {
|
||||||
throw new InvalidMaintenanceScheduleException(
|
throw new InvalidMaintenanceScheduleException("All of schedule, duration and timezone should either be null or non empty.");
|
||||||
"All of schedule, duration and timezone should either be null or non empty.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert the time interval or duration specified in "HH:mm:ss" format to
|
* Convert the time interval or duration specified in "HH:mm:ss" format to ISO format.
|
||||||
* ISO format.
|
|
||||||
*
|
*
|
||||||
* @param timeInterval in "HH:mm:ss" string format. This format is popularly used but
|
* @param timeInterval in "HH:mm:ss" string format. This format is popularly used but can be confused with time of the day,
|
||||||
* can be confused with time of the day, hence conversion to ISO
|
* hence conversion to ISO specified format for time duration is required.
|
||||||
* specified format for time duration is required.
|
|
||||||
* @return {@link Duration} in ISO format.
|
* @return {@link Duration} in ISO format.
|
||||||
* @throws DateTimeParseException if the text cannot be converted to ISO format.
|
* @throws DateTimeParseException if the text cannot be converted to ISO format.
|
||||||
*/
|
*/
|
||||||
@@ -136,11 +114,9 @@ public final class MaintenanceScheduleHelper {
|
|||||||
/**
|
/**
|
||||||
* Validates the format of the maintenance window duration
|
* Validates the format of the maintenance window duration
|
||||||
*
|
*
|
||||||
* @param duration in "HH:mm:ss" string format. This format is popularly used but
|
* @param duration in "HH:mm:ss" string format. This format is popularly used but can be confused with time of the day, hence conversion
|
||||||
* can be confused with time of the day, hence conversion to ISO
|
* to ISO specified format for time duration is required.
|
||||||
* specified format for time duration is required.
|
* @throws InvalidMaintenanceScheduleException if the duration doesn't have a valid format to be converted to ISO.
|
||||||
* @throws InvalidMaintenanceScheduleException if the duration doesn't have a valid format to be converted
|
|
||||||
* to ISO.
|
|
||||||
*/
|
*/
|
||||||
public static void validateDuration(final String duration) {
|
public static void validateDuration(final String duration) {
|
||||||
try {
|
try {
|
||||||
@@ -155,9 +131,8 @@ public final class MaintenanceScheduleHelper {
|
|||||||
/**
|
/**
|
||||||
* Validates the format of the maintenance window cron expression
|
* Validates the format of the maintenance window cron expression
|
||||||
*
|
*
|
||||||
* @param cronSchedule is a cron expression with 6 mandatory fields and 1 last
|
* @param cronSchedule is a cron expression with 6 mandatory fields and 1 last optional field: "second minute hour day-of-month month
|
||||||
* optional field: "second minute hour dayofmonth month weekday
|
* weekday year".
|
||||||
* year".
|
|
||||||
* @throws InvalidMaintenanceScheduleException if the cron expression doesn't have a valid quartz format.
|
* @throws InvalidMaintenanceScheduleException if the cron expression doesn't have a valid quartz format.
|
||||||
*/
|
*/
|
||||||
public static void validateCronSchedule(final String cronSchedule) {
|
public static void validateCronSchedule(final String cronSchedule) {
|
||||||
@@ -181,4 +156,4 @@ public final class MaintenanceScheduleHelper {
|
|||||||
private static LocalTime convertDurationToLocalTime(final String timeInterval) {
|
private static LocalTime convertDurationToLocalTime(final String timeInterval) {
|
||||||
return LocalTime.parse(timeInterval.strip());
|
return LocalTime.parse(timeInterval.strip());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,15 +21,11 @@ import jakarta.validation.Payload;
|
|||||||
* Constraint for strings submitted into the repository.
|
* Constraint for strings submitted into the repository.
|
||||||
*/
|
*/
|
||||||
@Constraint(validatedBy = ValidStringValidator.class)
|
@Constraint(validatedBy = ValidStringValidator.class)
|
||||||
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR,
|
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE })
|
||||||
ElementType.PARAMETER, ElementType.TYPE_USE })
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface ValidString {
|
public @interface ValidString {
|
||||||
|
|
||||||
String message() default "Invalid characters in string";
|
String message() default "Invalid characters in string";
|
||||||
|
|
||||||
Class<?>[] groups() default {};
|
Class<?>[] groups() default {};
|
||||||
|
|
||||||
Class<? extends Payload>[] payload() default {};
|
Class<? extends Payload>[] payload() default {};
|
||||||
|
}
|
||||||
}
|
|
||||||
@@ -49,7 +49,7 @@ public final class EventPublisherHolder {
|
|||||||
this.serviceMatcher = serviceMatcher;
|
this.serviceMatcher = serviceMatcher;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Autowired // spring setter injection
|
@Autowired(required = false) // spring setter injection
|
||||||
public void setBusProperties(final BusProperties bus) {
|
public void setBusProperties(final BusProperties bus) {
|
||||||
this.bus = bus;
|
this.bus = bus;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made
|
||||||
|
* available under the terms of the Eclipse Public License 2.0
|
||||||
|
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.eclipse.hawkbit.repository;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.aopalliance.intercept.MethodInvocation;
|
||||||
|
import org.eclipse.hawkbit.im.authentication.SpRole;
|
||||||
|
import org.eclipse.hawkbit.tenancy.configuration.ControllerPollProperties;
|
||||||
|
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Primary;
|
||||||
|
import org.springframework.context.annotation.PropertySource;
|
||||||
|
import org.springframework.expression.EvaluationContext;
|
||||||
|
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations;
|
||||||
|
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
||||||
|
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.util.function.SingletonSupplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default configuration that is common to all repository implementations.
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableMethodSecurity(proxyTargetClass = true, securedEnabled = true)
|
||||||
|
@EnableConfigurationProperties({ RepositoryProperties.class, ControllerPollProperties.class, TenantConfigurationProperties.class })
|
||||||
|
@PropertySource("classpath:/hawkbit-repository-defaults.properties")
|
||||||
|
public class RepositoryConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
static RoleHierarchy roleHierarchy() {
|
||||||
|
return RoleHierarchyImpl.fromHierarchy(SpRole.DEFAULT_ROLE_HIERARCHY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Primary
|
||||||
|
MethodSecurityExpressionHandler methodSecurityExpressionHandler(
|
||||||
|
final Optional<RoleHierarchy> roleHierarchy, final Optional<ApplicationContext> applicationContext) {
|
||||||
|
final DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler = new DefaultMethodSecurityExpressionHandler() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EvaluationContext createEvaluationContext(final Supplier<Authentication> authentication, final MethodInvocation mi) {
|
||||||
|
return super.createEvaluationContext(SingletonSupplier.of(() -> new RawAuthoritiesAuthentication(authentication.get())), mi);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(
|
||||||
|
final Authentication authentication, final MethodInvocation mi) {
|
||||||
|
return super.createSecurityExpressionRoot(new RawAuthoritiesAuthentication(authentication), mi);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
roleHierarchy.ifPresent(methodSecurityExpressionHandler::setRoleHierarchy);
|
||||||
|
applicationContext.ifPresent(methodSecurityExpressionHandler::setApplicationContext);
|
||||||
|
return methodSecurityExpressionHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class RawAuthoritiesAuthentication implements Authentication {
|
||||||
|
|
||||||
|
private final Authentication authentication;
|
||||||
|
private final transient SingletonSupplier<List<? extends GrantedAuthority>> rawAuthoritiesSupplier;
|
||||||
|
|
||||||
|
public RawAuthoritiesAuthentication(final Authentication authentication) {
|
||||||
|
this.authentication = authentication;
|
||||||
|
rawAuthoritiesSupplier = SingletonSupplier.of(
|
||||||
|
() -> authentication.getAuthorities().stream()
|
||||||
|
.map(GrantedAuthority::getAuthority)// get the authority
|
||||||
|
.map(authority -> {
|
||||||
|
// permissions are in the format UPDATE_TARGET(/<rsql query>).
|
||||||
|
// here we remove the rsql query - not supported by expression evaluation
|
||||||
|
// the rsql evaluation will be done later by the access controller
|
||||||
|
final int index = authority.indexOf('/');
|
||||||
|
return index < 0 ? authority : authority.substring(0, index);
|
||||||
|
})
|
||||||
|
.distinct() // remove duplicates if any
|
||||||
|
.map(SimpleGrantedAuthority::new)
|
||||||
|
.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||||
|
return rawAuthoritiesSupplier.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getCredentials() {
|
||||||
|
return authentication.getCredentials();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getDetails() {
|
||||||
|
return authentication.getDetails();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getPrincipal() {
|
||||||
|
return authentication.getPrincipal();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAuthenticated() {
|
||||||
|
return authentication.isAuthenticated();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAuthenticated(final boolean isAuthenticated) throws IllegalArgumentException {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return authentication.getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made
|
|
||||||
* available under the terms of the Eclipse Public License 2.0
|
|
||||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.eclipse.hawkbit.repository;
|
|
||||||
|
|
||||||
import org.eclipse.hawkbit.tenancy.configuration.ControllerPollProperties;
|
|
||||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
|
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.context.annotation.PropertySource;
|
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default configuration that is common to all repository implementations.
|
|
||||||
*/
|
|
||||||
@Configuration
|
|
||||||
@EnableMethodSecurity(proxyTargetClass = true, securedEnabled = true)
|
|
||||||
@EnableConfigurationProperties({ RepositoryProperties.class, ControllerPollProperties.class, TenantConfigurationProperties.class })
|
|
||||||
@PropertySource("classpath:/hawkbit-repository-defaults.properties")
|
|
||||||
public class RepositoryDefaultConfiguration {}
|
|
||||||
@@ -63,7 +63,7 @@ public class EntityMatcher {
|
|||||||
fieldGetter.setAccessible(true);
|
fieldGetter.setAccessible(true);
|
||||||
final Object fieldValue = fieldGetter.invoke(t);
|
final Object fieldValue = fieldGetter.invoke(t);
|
||||||
final Operator op = comparison.getOp();
|
final Operator op = comparison.getOp();
|
||||||
if (Map.class.isAssignableFrom(fieldGetter.getReturnType())) {
|
if (Map.class.isAssignableFrom(getReturnType(fieldGetter))) {
|
||||||
if ((op == NE || op == NOT_IN || op == NOT_LIKE)
|
if ((op == NE || op == NOT_IN || op == NOT_LIKE)
|
||||||
&& (fieldValue == null || !((Map<?, ?>) fieldValue).containsKey(split[1]))) {
|
&& (fieldValue == null || !((Map<?, ?>) fieldValue).containsKey(split[1]))) {
|
||||||
// TODO / recheck - when missing entity shall it be included or not in != or =out=? - now it's not
|
// TODO / recheck - when missing entity shall it be included or not in != or =out=? - now it's not
|
||||||
@@ -75,19 +75,19 @@ public class EntityMatcher {
|
|||||||
map(
|
map(
|
||||||
comparison.getValue(),
|
comparison.getValue(),
|
||||||
(Class<?>) ((ParameterizedType) fieldGetter.getGenericReturnType()).getActualTypeArguments()[1]));
|
(Class<?>) ((ParameterizedType) fieldGetter.getGenericReturnType()).getActualTypeArguments()[1]));
|
||||||
} else if (Collection.class.isAssignableFrom(fieldGetter.getReturnType())) { // Set / List
|
} else if (Collection.class.isAssignableFrom(getReturnType(fieldGetter))) { // Set / List
|
||||||
final Object value;
|
final Object value;
|
||||||
final BiFunction<Object, Operator, Boolean> compare;
|
final BiFunction<Object, Operator, Boolean> compare;
|
||||||
if (split.length == 1) {
|
if (split.length == 1) {
|
||||||
value = map(comparison.getValue(), fieldGetter.getReturnType());
|
value = map(comparison.getValue(), getReturnType(fieldGetter));
|
||||||
compare = (e, operator) -> compare(e, operator, value);
|
compare = (e, operator) -> compare(e, operator, value);
|
||||||
} else {
|
} else {
|
||||||
final Method valueGetter = getGetter(
|
final Method valueGetter = getGetter(
|
||||||
(Class<?>) ((ParameterizedType) fieldGetter.getGenericReturnType()).getActualTypeArguments()[0], split[1]);
|
(Class<?>) ((ParameterizedType) fieldGetter.getGenericReturnType()).getActualTypeArguments()[0], split[1]);
|
||||||
value = map(comparison.getValue(), valueGetter.getReturnType());
|
value = map(comparison.getValue(), getReturnType(valueGetter));
|
||||||
compare = (e, operator) -> {
|
compare = (e, operator) -> {
|
||||||
try {
|
try {
|
||||||
return compare(map(e == null ? null : valueGetter.invoke(e), valueGetter.getReturnType()), operator, value);
|
return compare(map(e == null ? null : valueGetter.invoke(e), getReturnType(valueGetter)), operator, value);
|
||||||
} catch (final IllegalAccessException | InvocationTargetException ex) {
|
} catch (final IllegalAccessException | InvocationTargetException ex) {
|
||||||
throw new IllegalArgumentException(ex);
|
throw new IllegalArgumentException(ex);
|
||||||
}
|
}
|
||||||
@@ -104,23 +104,23 @@ public class EntityMatcher {
|
|||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
if (split.length == 1) {
|
if (split.length == 1) {
|
||||||
return compare(fieldValue, op, map(comparison.getValue(), fieldGetter.getReturnType()));
|
return compare(fieldValue, op, map(comparison.getValue(), getReturnType(fieldGetter)));
|
||||||
} else {
|
} else {
|
||||||
if (split[1].contains(".")) {
|
if (split[1].contains(".")) {
|
||||||
// nested field access
|
// nested field access
|
||||||
final String[] nestedSplit = split[1].split("\\.", 2);
|
final String[] nestedSplit = split[1].split("\\.", 2);
|
||||||
final Method nestedFieldGetter = getGetter(fieldGetter.getReturnType(), nestedSplit[0]);
|
final Method nestedFieldGetter = getGetter(getReturnType(fieldGetter), nestedSplit[0]);
|
||||||
nestedFieldGetter.setAccessible(true);
|
nestedFieldGetter.setAccessible(true);
|
||||||
final Method valueGetter = getGetter(nestedFieldGetter.getReturnType(), nestedSplit[1]);
|
final Method valueGetter = getGetter(getReturnType(nestedFieldGetter), nestedSplit[1]);
|
||||||
final Object nestedFieldValue = fieldValue == null ? null : nestedFieldGetter.invoke(fieldValue);
|
final Object nestedFieldValue = fieldValue == null ? null : nestedFieldGetter.invoke(fieldValue);
|
||||||
return compare(
|
return compare(
|
||||||
nestedFieldValue == null ? null : valueGetter.invoke(nestedFieldValue),
|
nestedFieldValue == null ? null : valueGetter.invoke(nestedFieldValue),
|
||||||
op,
|
op,
|
||||||
map(comparison.getValue(), valueGetter.getReturnType()));
|
map(comparison.getValue(), getReturnType(valueGetter)));
|
||||||
} else {
|
} else {
|
||||||
final Method valueGetter = getGetter(fieldGetter.getReturnType(), split[1]);
|
final Method valueGetter = getGetter(getReturnType(fieldGetter), split[1]);
|
||||||
return compare(fieldValue == null ? null : valueGetter.invoke(fieldValue), op,
|
return compare(fieldValue == null ? null : valueGetter.invoke(fieldValue), op,
|
||||||
map(comparison.getValue(), valueGetter.getReturnType()));
|
map(comparison.getValue(), getReturnType(valueGetter)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,12 +142,24 @@ public class EntityMatcher {
|
|||||||
return Arrays.stream(t.getMethods())
|
return Arrays.stream(t.getMethods())
|
||||||
.filter(method -> getterLowercase.equals(method.getName().toLowerCase()))
|
.filter(method -> getterLowercase.equals(method.getName().toLowerCase()))
|
||||||
.findFirst()
|
.findFirst()
|
||||||
.map(method -> {
|
.map(Method::getName)
|
||||||
method.setAccessible(true);
|
.map(getterName -> {
|
||||||
return method;
|
try {
|
||||||
|
// gets method via Class.getMethod(String, Class<?>...) because in listing it might has no the
|
||||||
|
// correct return type, but the type got from a declaring generic type
|
||||||
|
final Method getter = t.getMethod(getterName);
|
||||||
|
getter.setAccessible(true);
|
||||||
|
return getter;
|
||||||
|
} catch (final NoSuchMethodException e) {
|
||||||
|
throw new IllegalStateException("Unexpected: No getter found for field: " + fieldName + " in class: " + t.getName(), e);
|
||||||
|
}
|
||||||
}).orElseThrow(() -> new NoSuchMethodException("No getter found for field: " + fieldName + " in class: " + t.getName()));
|
}).orElseThrow(() -> new NoSuchMethodException("No getter found for field: " + fieldName + " in class: " + t.getName()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Class<?> getReturnType(final Method valueGetter) {
|
||||||
|
return valueGetter.getReturnType();
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
private static Object map(final Object value, final Class<?> type) {
|
private static Object map(final Object value, final Class<?> type) {
|
||||||
if (value instanceof Collection<?> collection) { // in / out
|
if (value instanceof Collection<?> collection) { // in / out
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
|
|||||||
import org.eclipse.hawkbit.repository.EntityFactory;
|
import org.eclipse.hawkbit.repository.EntityFactory;
|
||||||
import org.eclipse.hawkbit.repository.PropertiesQuotaManagement;
|
import org.eclipse.hawkbit.repository.PropertiesQuotaManagement;
|
||||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||||
import org.eclipse.hawkbit.repository.RepositoryDefaultConfiguration;
|
import org.eclipse.hawkbit.repository.RepositoryConfiguration;
|
||||||
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
||||||
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
|
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
|
||||||
import org.eclipse.hawkbit.repository.RolloutExecutor;
|
import org.eclipse.hawkbit.repository.RolloutExecutor;
|
||||||
@@ -208,9 +208,9 @@ import org.springframework.validation.beanvalidation.MethodValidationPostProcess
|
|||||||
@EnableRetry
|
@EnableRetry
|
||||||
@EntityScan("org.eclipse.hawkbit.repository.jpa.model")
|
@EntityScan("org.eclipse.hawkbit.repository.jpa.model")
|
||||||
@PropertySource("classpath:/hawkbit-jpa-defaults.properties")
|
@PropertySource("classpath:/hawkbit-jpa-defaults.properties")
|
||||||
@Import({ JpaConfiguration.class, RepositoryDefaultConfiguration.class, LockProperties.class, DataSourceAutoConfiguration.class, SystemManagementCacheKeyGenerator.class })
|
@Import({ JpaConfiguration.class, RepositoryConfiguration.class, LockProperties.class, DataSourceAutoConfiguration.class, SystemManagementCacheKeyGenerator.class })
|
||||||
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
|
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
|
||||||
public class RepositoryApplicationConfiguration {
|
public class JpaRepositoryConfiguration {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines the validation processor bean.
|
* Defines the validation processor bean.
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made
|
||||||
|
* available under the terms of the Eclipse Public License 2.0
|
||||||
|
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.eclipse.hawkbit.repository.jpa.acm;
|
||||||
|
|
||||||
|
import static org.eclipse.hawkbit.security.SecurityContextTenantAware.SYSTEM_USER;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.EnumMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.eclipse.hawkbit.ContextAware;
|
||||||
|
import org.eclipse.hawkbit.repository.RsqlQueryField;
|
||||||
|
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.ql.EntityMatcher;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.rsql.RsqlUtility;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
|
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.util.ObjectUtils;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class DefaultAccessController<A extends Enum<A> & RsqlQueryField, T> implements AccessController<T> {
|
||||||
|
|
||||||
|
private final Class<A> rsqlQueryFieldType;
|
||||||
|
private final Map<Operation, List<String>> permissions = new EnumMap<>(Operation.class);
|
||||||
|
|
||||||
|
@Value("${hawkbit.jpa.security.default-access-controller.strict:false}")
|
||||||
|
private boolean strict;
|
||||||
|
private ContextAware contextAware;
|
||||||
|
private RoleHierarchy roleHierarchy;
|
||||||
|
|
||||||
|
public DefaultAccessController(final Class<A> rsqlQueryFieldType, final String... permissionTypes) {
|
||||||
|
if (ObjectUtils.isEmpty(permissionTypes)) {
|
||||||
|
throw new IllegalArgumentException("Permission types must not be empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rsqlQueryFieldType = rsqlQueryFieldType;
|
||||||
|
for (final Operation operation : Operation.values()) {
|
||||||
|
for (final String permissionType : permissionTypes) {
|
||||||
|
permissions.computeIfAbsent(operation, k -> new ArrayList<>()).add(operation.name() + "_" + permissionType.toUpperCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
void setContextAware(final ContextAware contextAware) {
|
||||||
|
this.contextAware = contextAware;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
void setRoleHierarchy(final RoleHierarchy roleHierarchy) {
|
||||||
|
this.roleHierarchy = roleHierarchy;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Specification<T>> getAccessRules(final Operation operation) {
|
||||||
|
if (contextAware.getCurrentTenant() != null && SYSTEM_USER.equals(contextAware.getCurrentUsername())) {
|
||||||
|
// as tenant, no restrictions
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Optional.ofNullable(getScopes(operation)) // if get scopes returns null, no scopes return no spec - all entities are accessible
|
||||||
|
.map(scopes -> // to RSQL
|
||||||
|
scopes.size() == 1
|
||||||
|
? scopes.get(0) // single scope
|
||||||
|
: "(" + String.join(") or (", scopes) + ")") // join multiple scopes with 'or' - union
|
||||||
|
.map(scope -> RsqlUtility.getInstance().buildRsqlSpecification(scope, rsqlQueryFieldType));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void assertOperationAllowed(final Operation operation, final T entity) throws InsufficientPermissionException {
|
||||||
|
if (contextAware.getCurrentTenant() != null && SYSTEM_USER.equals(contextAware.getCurrentUsername())) {
|
||||||
|
// as tenant, no restrictions
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<String> scopes = getScopes(operation);
|
||||||
|
if (scopes != null) {
|
||||||
|
for (final String scope : scopes) {
|
||||||
|
if (EntityMatcher.forRsql(scope).match(entity)) {
|
||||||
|
return; // at least one scope matches, operation is allowed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new InsufficientPermissionException(String.format("Operation '%s' is not allowed", operation));
|
||||||
|
} // else if scopes is null, no scopes are defined, so all entities are accessible
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns null if ALL entities are accessible, otherwise returns a list of scopes
|
||||||
|
// throws InsufficientPermissionException if no matching authority found (should not happen - should be already checked with @PreAuthorize)
|
||||||
|
// java:S1168 - returns null with purpose to indicate no scopes, privately used with attention
|
||||||
|
// java:S1168 - better readable at one place
|
||||||
|
@SuppressWarnings({ "java:S1168", "java:S1168" })
|
||||||
|
private List<String> getScopes(final Operation operation) {
|
||||||
|
final List<String> operationPermissions = permissions.get(operation);
|
||||||
|
final List<String> scopes = SecurityContextHolder.getContext().getAuthentication().getAuthorities().stream()
|
||||||
|
.map(GrantedAuthority::getAuthority)
|
||||||
|
.map(Permission::from)
|
||||||
|
.flatMap(permission -> roleHierarchy == null
|
||||||
|
? (operationPermissions.contains(permission.name()) ? Stream.of(permission) : Stream.empty())
|
||||||
|
: roleHierarchy.getReachableGrantedAuthorities(List.of(new SimpleGrantedAuthority(permission.name())))
|
||||||
|
.stream()
|
||||||
|
.map(GrantedAuthority::getAuthority)
|
||||||
|
.filter(operationPermissions::contains)
|
||||||
|
.map(reachableAuthority -> new Permission(reachableAuthority, permission.scope())))
|
||||||
|
.map(Permission::scope)
|
||||||
|
.distinct() // remove duplicates
|
||||||
|
.toList();
|
||||||
|
if (scopes.isEmpty()) {
|
||||||
|
// no matching authority found for the operation
|
||||||
|
// the needed permission should have already been checked with @PreAuthorize
|
||||||
|
// could happen, for instance, in controller management, that checks ROLE_CONTROLLER and on its behalf
|
||||||
|
// calls pure repository methods as privileged
|
||||||
|
if (strict) {
|
||||||
|
throw new InsufficientPermissionException(
|
||||||
|
String.format(
|
||||||
|
"No matching authority found for operation %s" +
|
||||||
|
" (expects %s, should not happen - shall have already been checked with @PreAuthorize)",
|
||||||
|
operation, operationPermissions));
|
||||||
|
} else {
|
||||||
|
// TODO - maybe in some future we could adapt permissions so controller roles to somehow apply what is needed
|
||||||
|
// and to do not "assume" and to throw exception always
|
||||||
|
log.debug(
|
||||||
|
"[{}] No matching authority found for operation {} (expects {}), they shall have already been checked with @PreAuthorize)",
|
||||||
|
rsqlQueryFieldType, operation, operationPermissions);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else if (scopes.contains(null)) {
|
||||||
|
return null; // not scoped at all
|
||||||
|
} else {
|
||||||
|
return scopes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record Permission(String name, String scope) {
|
||||||
|
|
||||||
|
private static final Pattern PATTERN = Pattern.compile("^(?<name>[^/]+)(/(?<scope>.+))?$");
|
||||||
|
|
||||||
|
static Permission from(final String authority) {
|
||||||
|
final Matcher matcher = PATTERN.matcher(authority);
|
||||||
|
if (!matcher.matches()) {
|
||||||
|
throw new IllegalArgumentException("Invalid authority format: " + authority);
|
||||||
|
}
|
||||||
|
return from(matcher);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Permission from(final Matcher matcher) {
|
||||||
|
return new Permission(matcher.group("name"), matcher.group("scope"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made
|
||||||
|
* available under the terms of the Eclipse Public License 2.0
|
||||||
|
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.eclipse.hawkbit.repository.jpa.acm;
|
||||||
|
|
||||||
|
import org.eclipse.hawkbit.repository.DistributionSetFields;
|
||||||
|
import org.eclipse.hawkbit.repository.TargetFields;
|
||||||
|
import org.eclipse.hawkbit.repository.TargetTypeFields;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ConditionalOnProperty(name = "hawkbit.acm.access-controller.enabled", havingValue = "true", matchIfMissing = true)
|
||||||
|
public class DefaultAccessControllerConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(name = "hawkbit.acm.access-controller.target.enabled", havingValue = "true", matchIfMissing = true)
|
||||||
|
AccessController<JpaTarget> targetAccessController() {
|
||||||
|
return new DefaultAccessController<>(TargetFields.class, "TARGET");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(name = "hawkbit.acm.access-controller.target-type.enabled", havingValue = "true", matchIfMissing = true)
|
||||||
|
AccessController<JpaTargetType> targetTypeAccessController() {
|
||||||
|
return new DefaultAccessController<>(TargetTypeFields.class, "TARGET", "TARGET_TYPE");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(name = "hawkbit.acm.access-controller.distribution-set.enabled", havingValue = "true", matchIfMissing = true)
|
||||||
|
AccessController<JpaDistributionSet> distributionSetAccessController() {
|
||||||
|
return new DefaultAccessController<>(DistributionSetFields.class, "REPOSITORY", "DISTRIBUTION_SET");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ import org.springframework.integration.support.MutableMessageHeaders;
|
|||||||
import org.springframework.messaging.Message;
|
import org.springframework.messaging.Message;
|
||||||
import org.springframework.messaging.MessageHeaders;
|
import org.springframework.messaging.MessageHeaders;
|
||||||
import org.springframework.messaging.converter.AbstractMessageConverter;
|
import org.springframework.messaging.converter.AbstractMessageConverter;
|
||||||
|
import org.springframework.test.context.TestPropertySource;
|
||||||
import org.springframework.test.util.ReflectionTestUtils;
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.util.MimeType;
|
import org.springframework.util.MimeType;
|
||||||
@@ -37,28 +38,22 @@ import org.springframework.util.MimeTypeUtils;
|
|||||||
/**
|
/**
|
||||||
* Test the remote entity events.
|
* Test the remote entity events.
|
||||||
*/
|
*/
|
||||||
|
@TestPropertySource(properties = { "spring.cloud.bus.enabled=true" })
|
||||||
@SuppressWarnings("java:S6813") // constructor injects are not possible for test classes
|
@SuppressWarnings("java:S6813") // constructor injects are not possible for test classes
|
||||||
public abstract class AbstractRemoteEventTest extends AbstractJpaIntegrationTest {
|
public abstract class AbstractRemoteEventTest extends AbstractJpaIntegrationTest {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private BusProtoStuffMessageConverter busProtoStuffMessageConverter;
|
private BusProtoStuffMessageConverter busProtoStuffMessageConverter;
|
||||||
|
|
||||||
private AbstractMessageConverter jacksonMessageConverter;
|
private AbstractMessageConverter jacksonMessageConverter;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setup() throws Exception {
|
public void setup() throws Exception {
|
||||||
final BusJacksonAutoConfiguration autoConfiguration = new BusJacksonAutoConfiguration();
|
final BusJacksonAutoConfiguration autoConfiguration = new BusJacksonAutoConfiguration();
|
||||||
this.jacksonMessageConverter = autoConfiguration.busJsonConverter(null);
|
this.jacksonMessageConverter = autoConfiguration.busJsonConverter(null);
|
||||||
ReflectionTestUtils.setField(jacksonMessageConverter, "packagesToScan",
|
ReflectionTestUtils.setField(
|
||||||
new String[] { "org.eclipse.hawkbit.repository.event.remote",
|
jacksonMessageConverter, "packagesToScan",
|
||||||
ClassUtils.getPackageName(RemoteApplicationEvent.class) });
|
new String[] { "org.eclipse.hawkbit.repository.event.remote", ClassUtils.getPackageName(RemoteApplicationEvent.class) });
|
||||||
((InitializingBean) jacksonMessageConverter).afterPropertiesSet();
|
((InitializingBean) jacksonMessageConverter).afterPropertiesSet();
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Message<?> createMessageWithImmutableHeader(final TenantAwareEvent event) {
|
|
||||||
final Map<String, Object> headers = new LinkedHashMap<>();
|
|
||||||
return busProtoStuffMessageConverter.toMessage(event, new MessageHeaders(headers));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@@ -90,4 +85,4 @@ import org.springframework.util.MimeTypeUtils;
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -27,8 +27,8 @@ import org.junit.jupiter.api.Test;
|
|||||||
*/
|
*/
|
||||||
class RemoteIdEventTest extends AbstractRemoteEventTest {
|
class RemoteIdEventTest extends AbstractRemoteEventTest {
|
||||||
|
|
||||||
private static final long ENTITY_ID = 1L;
|
|
||||||
private static final String TENANT = "tenant";
|
private static final String TENANT = "tenant";
|
||||||
|
private static final long ENTITY_ID = 1L;
|
||||||
private static final Class<? extends TenantAwareBaseEntity> ENTITY_CLASS = JpaAction.class;
|
private static final Class<? extends TenantAwareBaseEntity> ENTITY_CLASS = JpaAction.class;
|
||||||
private static final String CONTROLLER_ID = "controller911";
|
private static final String CONTROLLER_ID = "controller911";
|
||||||
private static final String ADDRESS = "amqp://anyhost";
|
private static final String ADDRESS = "amqp://anyhost";
|
||||||
@@ -109,4 +109,4 @@ class RemoteIdEventTest extends AbstractRemoteEventTest {
|
|||||||
// gets added because events inherit from of java.util.EventObject
|
// gets added because events inherit from of java.util.EventObject
|
||||||
assertThat(underTestCreatedEvent).usingRecursiveComparison().ignoringFields("source").isEqualTo(event);
|
assertThat(underTestCreatedEvent).usingRecursiveComparison().ignoringFields("source").isEqualTo(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,6 @@ class RemoteTenantAwareEventTest extends AbstractRemoteEventTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void testTargetAssignDistributionSetEvent() {
|
void testTargetAssignDistributionSetEvent() {
|
||||||
|
|
||||||
final DistributionSet dsA = testdataFactory.createDistributionSet("");
|
final DistributionSet dsA = testdataFactory.createDistributionSet("");
|
||||||
|
|
||||||
final JpaAction generateAction = new JpaAction();
|
final JpaAction generateAction = new JpaAction();
|
||||||
@@ -114,7 +113,6 @@ class RemoteTenantAwareEventTest extends AbstractRemoteEventTest {
|
|||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
void testCancelTargetAssignmentEvent() {
|
void testCancelTargetAssignmentEvent() {
|
||||||
|
|
||||||
final DistributionSet dsA = testdataFactory.createDistributionSet("");
|
final DistributionSet dsA = testdataFactory.createDistributionSet("");
|
||||||
|
|
||||||
final JpaAction generateAction = new JpaAction();
|
final JpaAction generateAction = new JpaAction();
|
||||||
@@ -145,9 +143,7 @@ class RemoteTenantAwareEventTest extends AbstractRemoteEventTest {
|
|||||||
return generateAction;
|
return generateAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertTargetAssignDistributionSetEvent(final Action action,
|
private void assertTargetAssignDistributionSetEvent(final Action action, final TargetAssignDistributionSetEvent underTest) {
|
||||||
final TargetAssignDistributionSetEvent underTest) {
|
|
||||||
|
|
||||||
assertThat(underTest.getActions()).hasSize(1);
|
assertThat(underTest.getActions()).hasSize(1);
|
||||||
final ActionProperties actionProperties = underTest.getActions().get(action.getTarget().getControllerId());
|
final ActionProperties actionProperties = underTest.getActions().get(action.getTarget().getControllerId());
|
||||||
assertThat(actionProperties).isNotNull();
|
assertThat(actionProperties).isNotNull();
|
||||||
@@ -162,4 +158,4 @@ class RemoteTenantAwareEventTest extends AbstractRemoteEventTest {
|
|||||||
assertThat(actionProperties).usingRecursiveComparison().comparingOnlyFields().isEqualTo(new ActionProperties(action));
|
assertThat(actionProperties).usingRecursiveComparison().comparingOnlyFields().isEqualTo(new ActionProperties(action));
|
||||||
assertThat(underTest.getActionPropertiesForController(action.getTarget().getControllerId())).isPresent();
|
assertThat(underTest.getActionPropertiesForController(action.getTarget().getControllerId())).isPresent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,4 +70,4 @@ public abstract class AbstractRemoteEntityEventTest<E> extends AbstractRemoteEve
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected abstract E createEntity();
|
protected abstract E createEntity();
|
||||||
}
|
}
|
||||||
@@ -90,5 +90,4 @@ class ActionEventTest extends AbstractRemoteEntityEventTest<Action> {
|
|||||||
generateAction.setWeight(1000);
|
generateAction.setWeight(1000);
|
||||||
return actionRepository.save(generateAction);
|
return actionRepository.save(generateAction);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
@@ -30,7 +30,7 @@ class DistributionSetCreatedEventTest extends AbstractRemoteEntityEventTest<Dist
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DistributionSet createEntity() {
|
protected DistributionSet createEntity() {
|
||||||
return distributionSetManagement.create(entityFactory.distributionSet().create()
|
return distributionSetManagement.create(
|
||||||
.name("incomplete").version("2").description("incomplete").type("os"));
|
entityFactory.distributionSet().create().name("incomplete").version("2").description("incomplete").type("os"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,7 @@ class DistributionSetUpdatedEventTest extends AbstractRemoteEntityEventTest<Dist
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected DistributionSet createEntity() {
|
protected DistributionSet createEntity() {
|
||||||
return distributionSetManagement.create(entityFactory.distributionSet().create()
|
return distributionSetManagement.create(
|
||||||
.name("incomplete").version("2").description("incomplete").type("os"));
|
entityFactory.distributionSet().create().name("incomplete").version("2").description("incomplete").type("os"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,8 +44,9 @@ class RolloutEventTest extends AbstractRemoteEntityEventTest<Rollout> {
|
|||||||
.type("os").modules(Collections.singletonList(module.getId())));
|
.type("os").modules(Collections.singletonList(module.getId())));
|
||||||
|
|
||||||
return rolloutManagement.create(
|
return rolloutManagement.create(
|
||||||
entityFactory.rollout().create().name("exampleRollout").targetFilterQuery("controllerId==*").distributionSetId(ds), 5,
|
entityFactory.rollout().create().name("exampleRollout").targetFilterQuery("controllerId==*").distributionSetId(ds),
|
||||||
false, new RolloutGroupConditionBuilder().withDefaults()
|
5,
|
||||||
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, "10").build());
|
false,
|
||||||
|
new RolloutGroupConditionBuilder().withDefaults().successCondition(RolloutGroupSuccessCondition.THRESHOLD, "10").build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,7 @@ package org.eclipse.hawkbit.repository.event.remote.entity;
|
|||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||||
@@ -81,13 +81,16 @@ class RolloutGroupEventTest extends AbstractRemoteEntityEventTest<RolloutGroup>
|
|||||||
final SoftwareModule module = softwareModuleManagement.create(
|
final SoftwareModule module = softwareModuleManagement.create(
|
||||||
entityFactory.softwareModule().create().name("swm").version("2").description("desc").type("os"));
|
entityFactory.softwareModule().create().name("swm").version("2").description("desc").type("os"));
|
||||||
final DistributionSet ds = distributionSetManagement
|
final DistributionSet ds = distributionSetManagement
|
||||||
.create(entityFactory.distributionSet().create().name("complete").version("2").description("complete")
|
.create(entityFactory.distributionSet().create()
|
||||||
.type("os").modules(Collections.singletonList(module.getId())));
|
.name("complete").version("2")
|
||||||
|
.description("complete").type("os")
|
||||||
|
.modules(List.of(module.getId())));
|
||||||
|
|
||||||
final Rollout entity = rolloutManagement.create(
|
final Rollout entity = rolloutManagement.create(
|
||||||
entityFactory.rollout().create().name("exampleRollout").targetFilterQuery("controllerId==*").distributionSetId(ds), 5,
|
entityFactory.rollout().create().name("exampleRollout").targetFilterQuery("controllerId==*").distributionSetId(ds),
|
||||||
false, new RolloutGroupConditionBuilder().withDefaults()
|
5,
|
||||||
.successCondition(RolloutGroupSuccessCondition.THRESHOLD, "10").build());
|
false,
|
||||||
|
new RolloutGroupConditionBuilder().withDefaults().successCondition(RolloutGroupSuccessCondition.THRESHOLD, "10").build());
|
||||||
|
|
||||||
return rolloutGroupManagement.findByRollout(entity.getId(), PAGE).getContent().get(0);
|
return rolloutGroupManagement.findByRollout(entity.getId(), PAGE).getContent().get(0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,5 +40,4 @@ class TargetEventTest extends AbstractRemoteEntityEventTest<Target> {
|
|||||||
protected Target createEntity() {
|
protected Target createEntity() {
|
||||||
return testdataFactory.createTarget("12345");
|
return testdataFactory.createTarget("12345");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
@@ -40,5 +40,4 @@ class TargetTagEventTest extends AbstractRemoteEntityEventTest<TargetTag> {
|
|||||||
protected TargetTag createEntity() {
|
protected TargetTag createEntity() {
|
||||||
return targetTagManagement.create(entityFactory.tag().create().name("tag1"));
|
return targetTagManagement.create(entityFactory.tag().create().name("tag1"));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
@@ -10,6 +10,8 @@
|
|||||||
package org.eclipse.hawkbit.repository.jpa;
|
package org.eclipse.hawkbit.repository.jpa;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.runAs;
|
||||||
|
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.withUser;
|
||||||
|
|
||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@@ -62,7 +64,6 @@ import org.eclipse.hawkbit.repository.model.TargetTypeAssignmentResult;
|
|||||||
import org.eclipse.hawkbit.repository.test.TestConfiguration;
|
import org.eclipse.hawkbit.repository.test.TestConfiguration;
|
||||||
import org.eclipse.hawkbit.repository.test.util.AbstractIntegrationTest;
|
import org.eclipse.hawkbit.repository.test.util.AbstractIntegrationTest;
|
||||||
import org.eclipse.hawkbit.repository.test.util.RolloutTestApprovalStrategy;
|
import org.eclipse.hawkbit.repository.test.util.RolloutTestApprovalStrategy;
|
||||||
import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch;
|
|
||||||
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
|
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
|
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
|
||||||
@@ -73,7 +74,7 @@ import org.springframework.test.context.TestPropertySource;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ContextConfiguration(classes = { RepositoryApplicationConfiguration.class, TestConfiguration.class })
|
@ContextConfiguration(classes = { JpaRepositoryConfiguration.class, TestConfiguration.class })
|
||||||
@TestPropertySource(locations = "classpath:/jpa-test.properties")
|
@TestPropertySource(locations = "classpath:/jpa-test.properties")
|
||||||
@SuppressWarnings("java:S6813") // constructor injects are not possible for test classes
|
@SuppressWarnings("java:S6813") // constructor injects are not possible for test classes
|
||||||
public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest {
|
public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest {
|
||||||
@@ -82,7 +83,9 @@ public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest
|
|||||||
protected static final String NOT_EXIST_ID = "12345678990";
|
protected static final String NOT_EXIST_ID = "12345678990";
|
||||||
protected static final long NOT_EXIST_IDL = Long.parseLong(NOT_EXIST_ID);
|
protected static final long NOT_EXIST_IDL = Long.parseLong(NOT_EXIST_ID);
|
||||||
|
|
||||||
private static final List<String> REPOSITORY_AND_TARGET_PERMISSIONS = List.of(SpPermission.READ_REPOSITORY, SpPermission.CREATE_REPOSITORY, SpPermission.UPDATE_REPOSITORY, SpPermission.DELETE_REPOSITORY, SpPermission.READ_TARGET, SpPermission.CREATE_TARGET, SpPermission.UPDATE_TARGET, SpPermission.DELETE_TARGET);
|
private static final List<String> REPOSITORY_AND_TARGET_PERMISSIONS = List.of(SpPermission.READ_REPOSITORY, SpPermission.CREATE_REPOSITORY,
|
||||||
|
SpPermission.UPDATE_REPOSITORY, SpPermission.DELETE_REPOSITORY, SpPermission.READ_TARGET, SpPermission.CREATE_TARGET,
|
||||||
|
SpPermission.UPDATE_TARGET, SpPermission.DELETE_TARGET);
|
||||||
|
|
||||||
@PersistenceContext
|
@PersistenceContext
|
||||||
protected EntityManager entityManager;
|
protected EntityManager entityManager;
|
||||||
@@ -230,33 +233,33 @@ public abstract class AbstractJpaIntegrationTest extends AbstractIntegrationTest
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the given callable throws an InsufficientPermissionException.
|
* Asserts that the given callable throws an InsufficientPermissionException.
|
||||||
|
*
|
||||||
* @param callable the callable to call
|
* @param callable the callable to call
|
||||||
* @param requiredPermissions required permissions for the callable
|
* @param requiredPermissions required permissions for the callable
|
||||||
* @param insufficientPermissions can be null, if null, it will be resolved automatically. But in some cases (e.g. @PreAuthorized Permissions with OR, it is safer to pass directly the insufficient permissions)
|
* @param insufficientPermissions can be null, if null, it will be resolved automatically. But in some cases (e.g. @PreAuthorized Permissions with OR, it is safer to pass directly the insufficient permissions)
|
||||||
*/
|
*/
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
protected void assertPermissions(final Callable<?> callable, final List<String> requiredPermissions, final List<String> insufficientPermissions) {
|
protected void assertPermissions(final Callable<?> callable, final List<String> requiredPermissions,
|
||||||
|
final List<String> insufficientPermissions) {
|
||||||
// if READ_PERMISSION is required and required permissions are multiple, give only READ_PERMISSION to eliminate internal read_permission check failure that would confuse the actual test
|
// if READ_PERMISSION is required and required permissions are multiple, give only READ_PERMISSION to eliminate internal read_permission check failure that would confuse the actual test
|
||||||
final List<String> resolvedInsufficientPermissions = insufficientPermissions != null ? insufficientPermissions :
|
final List<String> resolvedInsufficientPermissions = insufficientPermissions != null
|
||||||
requiredPermissions.contains(SpPermission.READ_REPOSITORY) && requiredPermissions.size() > 1 ?
|
? insufficientPermissions
|
||||||
List.of(SpPermission.READ_REPOSITORY) : REPOSITORY_AND_TARGET_PERMISSIONS.stream()
|
: requiredPermissions.contains(SpPermission.READ_REPOSITORY) && requiredPermissions.size() > 1
|
||||||
.filter(p -> !requiredPermissions.contains(p)).toList();
|
? List.of(SpPermission.READ_REPOSITORY)
|
||||||
|
: REPOSITORY_AND_TARGET_PERMISSIONS.stream().filter(p -> !requiredPermissions.contains(p)).toList();
|
||||||
// check if the user has the correct permissions
|
// check if the user has the correct permissions
|
||||||
SecurityContextSwitch.runAs(SecurityContextSwitch.withUser("user_with_permissions", requiredPermissions.toArray(new String[0])), () -> {
|
runAs(withUser("user_with_permissions", requiredPermissions.toArray(new String[0])), () -> {
|
||||||
assertPermissionWorks(callable);
|
assertPermissionWorks(callable);
|
||||||
log.info("assertPermissionWorks Passed");
|
log.info("assertPermissionWorks Passed");
|
||||||
return null;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// check if the user has the insufficient permissions
|
// check if the user has the insufficient permissions
|
||||||
SecurityContextSwitch.runAs(SecurityContextSwitch.withUser("user_without_permissions", resolvedInsufficientPermissions.toArray(new String[0])), () -> {
|
runAs(withUser("user_without_permissions", resolvedInsufficientPermissions.toArray(new String[0])), () -> {
|
||||||
assertInsufficientPermission(callable);
|
assertInsufficientPermission(callable);
|
||||||
log.info("assertInsufficientPermission Passed");
|
log.info("assertInsufficientPermission Passed");
|
||||||
return null;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asserts that the given callable throws an InsufficientPermissionException.
|
* Asserts that the given callable throws an InsufficientPermissionException.
|
||||||
* If callable succeeds without any exception or exception other than InsufficientPermissionException, it will be considered as an assert failure.
|
* If callable succeeds without any exception or exception other than InsufficientPermissionException, it will be considered as an assert failure.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (c) 2023 Bosch.IO GmbH and others
|
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||||
*
|
*
|
||||||
* This program and the accompanying materials are made
|
* This program and the accompanying materials are made
|
||||||
* available under the terms of the Eclipse Public License 2.0
|
* available under the terms of the Eclipse Public License 2.0
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.repository.jpa.acm.context;
|
package org.eclipse.hawkbit.repository.jpa.acm;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
@@ -0,0 +1,274 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made
|
||||||
|
* available under the terms of the Eclipse Public License 2.0
|
||||||
|
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.eclipse.hawkbit.repository.jpa.acm;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_REPOSITORY;
|
||||||
|
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_TARGET;
|
||||||
|
import static org.eclipse.hawkbit.im.authentication.SpPermission.UPDATE_REPOSITORY;
|
||||||
|
import static org.eclipse.hawkbit.im.authentication.SpPermission.UPDATE_TARGET;
|
||||||
|
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.runAs;
|
||||||
|
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.withUser;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.eclipse.hawkbit.repository.Identifiable;
|
||||||
|
import org.eclipse.hawkbit.repository.builder.AutoAssignDistributionSetUpdate;
|
||||||
|
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||||
|
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
|
||||||
|
import org.eclipse.hawkbit.repository.model.Action;
|
||||||
|
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||||
|
import org.eclipse.hawkbit.repository.model.DistributionSetFilter;
|
||||||
|
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
||||||
|
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feature: Component Tests - Access Control<br/>
|
||||||
|
* Story: Test Distribution Set Access Controller
|
||||||
|
*/
|
||||||
|
@ContextConfiguration(classes = { DefaultAccessControllerConfiguration.class })
|
||||||
|
class DistributionSetAccessControllerTest extends AbstractJpaIntegrationTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies read access rules for distribution sets
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void verifyDistributionSetReadOperations() {
|
||||||
|
final DistributionSet permitted = testdataFactory.createDistributionSet();
|
||||||
|
final DistributionSet hidden = testdataFactory.createDistributionSet();
|
||||||
|
|
||||||
|
final Action permittedAction = testdataFactory.performAssignment(permitted);
|
||||||
|
final Action hiddenAction = testdataFactory.performAssignment(hidden);
|
||||||
|
|
||||||
|
runAs(withUser("user",
|
||||||
|
READ_REPOSITORY + "/id==" + permitted.getId(),
|
||||||
|
READ_TARGET +"/controllerId==" + permittedAction.getTarget().getControllerId()), () -> {
|
||||||
|
final Long permittedActionId = permitted.getId();
|
||||||
|
|
||||||
|
// verify distributionSetManagement#findAll
|
||||||
|
assertThat(distributionSetManagement.findAll(Pageable.unpaged()).get().map(Identifiable::getId).toList())
|
||||||
|
.containsOnly(permittedActionId);
|
||||||
|
|
||||||
|
// verify distributionSetManagement#findByRsql
|
||||||
|
assertThat(distributionSetManagement.findByRsql("name==*", Pageable.unpaged()).get().map(Identifiable::getId)
|
||||||
|
.toList()).containsOnly(permittedActionId);
|
||||||
|
|
||||||
|
// verify distributionSetManagement#findByCompleted
|
||||||
|
assertThat(distributionSetManagement.findByCompleted(true, Pageable.unpaged()).get().map(Identifiable::getId)
|
||||||
|
.toList()).containsOnly(permittedActionId);
|
||||||
|
|
||||||
|
// verify distributionSetManagement#findByDistributionSetFilter
|
||||||
|
assertThat(distributionSetManagement
|
||||||
|
.findByDistributionSetFilter(DistributionSetFilter.builder().isDeleted(false).build(), Pageable.unpaged())
|
||||||
|
.get().map(Identifiable::getId).toList()).containsOnly(permittedActionId);
|
||||||
|
|
||||||
|
// verify distributionSetManagement#get
|
||||||
|
assertThat(distributionSetManagement.get(permittedActionId)).isPresent();
|
||||||
|
final Long hiddenId = hidden.getId();
|
||||||
|
assertThat(distributionSetManagement.get(hiddenId)).isEmpty();
|
||||||
|
|
||||||
|
// verify distributionSetManagement#getWithDetails
|
||||||
|
assertThat(distributionSetManagement.getWithDetails(permittedActionId)).isPresent();
|
||||||
|
assertThat(distributionSetManagement.getWithDetails(hiddenId)).isEmpty();
|
||||||
|
|
||||||
|
// verify distributionSetManagement#get
|
||||||
|
assertThat(distributionSetManagement.getValid(permittedActionId).getId()).isEqualTo(permittedActionId);
|
||||||
|
assertThatThrownBy(() -> distributionSetManagement.getValid(hiddenId))
|
||||||
|
.as("Distribution set should not be found.").isInstanceOf(EntityNotFoundException.class);
|
||||||
|
|
||||||
|
// verify distributionSetManagement#get
|
||||||
|
final List<Long> allActionIds = Arrays.asList(permittedActionId, hiddenId);
|
||||||
|
assertThatThrownBy(() -> distributionSetManagement.get(allActionIds))
|
||||||
|
.as("Fail if request hidden.").isInstanceOf(EntityNotFoundException.class);
|
||||||
|
|
||||||
|
// verify distributionSetManagement#getByNameAndVersion
|
||||||
|
assertThat(distributionSetManagement.findByNameAndVersion(permitted.getName(), permitted.getVersion())).isPresent();
|
||||||
|
assertThat(distributionSetManagement.findByNameAndVersion(hidden.getName(), hidden.getVersion())).isEmpty();
|
||||||
|
|
||||||
|
// verify distributionSetManagement#getByAction
|
||||||
|
assertThat(distributionSetManagement.findByAction(permittedAction.getId())).isPresent();
|
||||||
|
final Long hiddenActionId = hiddenAction.getId();
|
||||||
|
assertThatThrownBy(() -> distributionSetManagement.findByAction(hiddenActionId))
|
||||||
|
.as("Action is hidden.").isInstanceOf(InsufficientPermissionException.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies read access rules for distribution sets
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void verifyDistributionSetUpdates() {
|
||||||
|
final DistributionSet permitted = testdataFactory.createDistributionSet();
|
||||||
|
final String mdPresetKey = "metadata.preset";
|
||||||
|
final String mdPresetValue = "presetValue";
|
||||||
|
distributionSetManagement.createMetadata(permitted.getId(), Map.of(mdPresetKey, mdPresetValue));
|
||||||
|
final DistributionSet readOnly = testdataFactory.createDistributionSet();
|
||||||
|
distributionSetManagement.createMetadata(readOnly.getId(), Map.of(mdPresetKey, mdPresetValue));
|
||||||
|
final DistributionSet hidden = testdataFactory.createDistributionSet();
|
||||||
|
distributionSetManagement.createMetadata(hidden.getId(), Map.of(mdPresetKey, mdPresetValue));
|
||||||
|
|
||||||
|
final SoftwareModule swModule = testdataFactory.createSoftwareModuleOs();
|
||||||
|
|
||||||
|
runAs(withUser("user",
|
||||||
|
READ_REPOSITORY + "/id==" + permitted.getId() + " or id==" + readOnly.getId(),
|
||||||
|
UPDATE_REPOSITORY + "/id==" + permitted.getId()), () -> {
|
||||||
|
// verify distributionSetManagement#assignSoftwareModules
|
||||||
|
final List<Long> singleModuleIdList = Collections.singletonList(swModule.getId());
|
||||||
|
assertThat(distributionSetManagement.assignSoftwareModules(permitted.getId(), singleModuleIdList))
|
||||||
|
.satisfies(ds -> assertThat(ds.getModules().stream().map(Identifiable::getId).toList()).contains(swModule.getId()));
|
||||||
|
final Long readOnlyId = readOnly.getId();
|
||||||
|
assertThatThrownBy(() -> distributionSetManagement.assignSoftwareModules(readOnlyId, singleModuleIdList))
|
||||||
|
.as("Distribution set not allowed to me modified.")
|
||||||
|
.isInstanceOf(InsufficientPermissionException.class);
|
||||||
|
final Long hiddenId = hidden.getId();
|
||||||
|
assertThatThrownBy(() -> distributionSetManagement.assignSoftwareModules(hiddenId, singleModuleIdList))
|
||||||
|
.as("Distribution set should not be visible.")
|
||||||
|
.isInstanceOf(EntityNotFoundException.class);
|
||||||
|
|
||||||
|
final Map<String, String> metadata = Map.of("test.create", mdPresetValue);
|
||||||
|
|
||||||
|
// verify distributionSetManagement#createMetaData
|
||||||
|
distributionSetManagement.createMetadata(permitted.getId(), metadata);
|
||||||
|
assertThatThrownBy(() -> distributionSetManagement.createMetadata(readOnlyId, metadata))
|
||||||
|
.as("Distribution set not allowed to be modified.")
|
||||||
|
.isInstanceOf(InsufficientPermissionException.class);
|
||||||
|
assertThatThrownBy(() -> distributionSetManagement.createMetadata(hiddenId, metadata))
|
||||||
|
.as("Distribution set should not be visible.")
|
||||||
|
.isInstanceOf(EntityNotFoundException.class);
|
||||||
|
|
||||||
|
// verify distributionSetManagement#updateMetaData
|
||||||
|
final String newValue = "newValue";
|
||||||
|
distributionSetManagement.updateMetadata(permitted.getId(), mdPresetKey, newValue);
|
||||||
|
assertThatThrownBy(() -> distributionSetManagement.updateMetadata(readOnlyId, mdPresetKey, newValue))
|
||||||
|
.as("Distribution set not allowed to me modified.")
|
||||||
|
.isInstanceOf(InsufficientPermissionException.class);
|
||||||
|
assertThatThrownBy(() -> distributionSetManagement.updateMetadata(hiddenId, mdPresetKey, newValue))
|
||||||
|
.as("Distribution set should not be visible.")
|
||||||
|
.isInstanceOf(EntityNotFoundException.class);
|
||||||
|
|
||||||
|
// verify distributionSetManagement#deleteMetaData
|
||||||
|
final String metadataKey = metadata.entrySet().stream().findAny().get().getKey();
|
||||||
|
distributionSetManagement.deleteMetadata(permitted.getId(), metadataKey);
|
||||||
|
assertThatThrownBy(() -> distributionSetManagement.deleteMetadata(readOnlyId, mdPresetKey))
|
||||||
|
.as("Distribution set not allowed to me modified.")
|
||||||
|
.isInstanceOf(InsufficientPermissionException.class);
|
||||||
|
assertThatThrownBy(() -> distributionSetManagement.deleteMetadata(hiddenId, mdPresetKey))
|
||||||
|
.as("Distribution set should not be visible.")
|
||||||
|
.isInstanceOf(EntityNotFoundException.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void verifyTagFilteringAndManagement() {
|
||||||
|
final DistributionSet permitted = testdataFactory.createDistributionSet();
|
||||||
|
final DistributionSet readOnly = testdataFactory.createDistributionSet();
|
||||||
|
final DistributionSet hidden = testdataFactory.createDistributionSet();
|
||||||
|
final Long dsTagId = distributionSetTagManagement.create(entityFactory.tag().create().name("dsTag")).getId();
|
||||||
|
final Long dsTag2Id = distributionSetTagManagement.create(entityFactory.tag().create().name("dsTag2")).getId();
|
||||||
|
|
||||||
|
// perform tag assignment before setting access rules
|
||||||
|
distributionSetManagement.assignTag(Arrays.asList(permitted.getId(), readOnly.getId(), hidden.getId()), dsTagId);
|
||||||
|
|
||||||
|
runAs(withUser("user",
|
||||||
|
READ_REPOSITORY + "/id==" + permitted.getId() + " or id==" + readOnly.getId(),
|
||||||
|
UPDATE_REPOSITORY + "/id==" + permitted.getId()), () -> {
|
||||||
|
assertThat(distributionSetManagement.findByTag(dsTagId, Pageable.unpaged()).get().map(Identifiable::getId)
|
||||||
|
.toList()).containsOnly(permitted.getId(), readOnly.getId());
|
||||||
|
|
||||||
|
assertThat(distributionSetManagement.findByRsqlAndTag("name==*", dsTagId, Pageable.unpaged()).get()
|
||||||
|
.map(Identifiable::getId).toList()).containsOnly(permitted.getId(), readOnly.getId());
|
||||||
|
|
||||||
|
// verify distributionSetManagement#unassignTag on permitted target
|
||||||
|
assertThat(distributionSetManagement
|
||||||
|
.unassignTag(Collections.singletonList(permitted.getId()), dsTagId))
|
||||||
|
.size()
|
||||||
|
.isEqualTo(1);
|
||||||
|
// verify distributionSetManagement#assignTag on permitted target
|
||||||
|
assertThat(distributionSetManagement.assignTag(Collections.singletonList(permitted.getId()), dsTagId))
|
||||||
|
.hasSize(1);
|
||||||
|
// verify distributionSetManagement#unAssignTag on permitted target
|
||||||
|
assertThat(distributionSetManagement.unassignTag(List.of(permitted.getId()), dsTagId)
|
||||||
|
.get(0).getId())
|
||||||
|
.isEqualTo(permitted.getId());
|
||||||
|
|
||||||
|
// assignment is denied for readOnlyTarget (read, but no update permissions)
|
||||||
|
final List<Long> readOblyList = Collections.singletonList(readOnly.getId());
|
||||||
|
assertThatThrownBy(() ->
|
||||||
|
distributionSetManagement.unassignTag(readOblyList, dsTagId))
|
||||||
|
.as("Missing update permissions for target to toggle tag assignment.")
|
||||||
|
.isInstanceOf(InsufficientPermissionException.class);
|
||||||
|
|
||||||
|
// assignment is denied for readOnlyTarget (read, but no update permissions)
|
||||||
|
// dsTag2- since - it is tagged with dsTag and won't do anything if assigning dsTag
|
||||||
|
assertThatThrownBy(() -> distributionSetManagement.assignTag(readOblyList, dsTag2Id))
|
||||||
|
.as("Missing update permissions for target to toggle tag assignment.")
|
||||||
|
.isInstanceOf(InsufficientPermissionException.class);
|
||||||
|
|
||||||
|
// assignment is denied for hiddenTarget since it's hidden
|
||||||
|
final List<Long> hiddenList = Collections.singletonList(hidden.getId());
|
||||||
|
assertThatThrownBy(() -> distributionSetManagement.unassignTag(hiddenList, dsTagId))
|
||||||
|
.as("Missing update permissions for target to toggle tag assignment.")
|
||||||
|
.isInstanceOf(EntityNotFoundException.class);
|
||||||
|
|
||||||
|
// assignment is denied for hiddenTarget since it's hidden
|
||||||
|
assertThatThrownBy(() -> distributionSetManagement.assignTag(hiddenList, dsTagId))
|
||||||
|
.as("Missing update permissions for target to toggle tag assignment.")
|
||||||
|
.isInstanceOf(EntityNotFoundException.class);
|
||||||
|
|
||||||
|
// assignment is denied for hiddenTarget since it's hidden
|
||||||
|
final List<Long> hiddenIdList = List.of(hidden.getId());
|
||||||
|
assertThatThrownBy(() -> distributionSetManagement.unassignTag(hiddenIdList, dsTagId))
|
||||||
|
.as("Missing update permissions for target to toggle tag assignment.")
|
||||||
|
.isInstanceOf(EntityNotFoundException.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void verifyAutoAssignmentUsage() {
|
||||||
|
final DistributionSet permitted = testdataFactory.createDistributionSet();
|
||||||
|
final DistributionSet readOnly = testdataFactory.createDistributionSet();
|
||||||
|
final DistributionSet hidden = testdataFactory.createDistributionSet();
|
||||||
|
// has to lock them, otherwise implicit lock shall be made which require DistributionSet update permissions
|
||||||
|
distributionSetManagement.lock(permitted.getId());
|
||||||
|
distributionSetManagement.lock(readOnly.getId());
|
||||||
|
distributionSetManagement.lock(hidden.getId());
|
||||||
|
|
||||||
|
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement
|
||||||
|
.create(entityFactory.targetFilterQuery().create().name("test").query("id==*"));
|
||||||
|
|
||||||
|
runAs(withUser("user",
|
||||||
|
READ_REPOSITORY + "/id==" + permitted.getId() + " or id==" + readOnly.getId(),
|
||||||
|
UPDATE_REPOSITORY + "/id==" + permitted.getId(),
|
||||||
|
// read / update target needed to update target filter query
|
||||||
|
READ_TARGET, UPDATE_TARGET), () -> {
|
||||||
|
assertThat(targetFilterQueryManagement
|
||||||
|
.updateAutoAssignDS(new AutoAssignDistributionSetUpdate(targetFilterQuery.getId()).ds(permitted.getId())
|
||||||
|
.actionType(Action.ActionType.FORCED).confirmationRequired(false))
|
||||||
|
.getAutoAssignDistributionSet().getId()).isEqualTo(permitted.getId());
|
||||||
|
targetFilterQueryManagement
|
||||||
|
.updateAutoAssignDS(new AutoAssignDistributionSetUpdate(targetFilterQuery.getId())
|
||||||
|
.ds(readOnly.getId()).actionType(Action.ActionType.FORCED).confirmationRequired(false))
|
||||||
|
.getAutoAssignDistributionSet().getId();
|
||||||
|
final AutoAssignDistributionSetUpdate autoAssignDistributionSetUpdate = new AutoAssignDistributionSetUpdate(
|
||||||
|
targetFilterQuery.getId())
|
||||||
|
.ds(hidden.getId()).actionType(Action.ActionType.FORCED).confirmationRequired(false);
|
||||||
|
assertThatThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(autoAssignDistributionSetUpdate))
|
||||||
|
.isInstanceOf(EntityNotFoundException.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,340 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2025 Contributors to the Eclipse Foundation
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made
|
||||||
|
* available under the terms of the Eclipse Public License 2.0
|
||||||
|
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.eclipse.hawkbit.repository.jpa.acm;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.eclipse.hawkbit.im.authentication.SpPermission.CREATE_ROLLOUT;
|
||||||
|
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_REPOSITORY;
|
||||||
|
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_ROLLOUT;
|
||||||
|
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_TARGET;
|
||||||
|
import static org.eclipse.hawkbit.im.authentication.SpPermission.UPDATE_TARGET;
|
||||||
|
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.runAs;
|
||||||
|
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.withUser;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.hawkbit.repository.FilterParams;
|
||||||
|
import org.eclipse.hawkbit.repository.Identifiable;
|
||||||
|
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.autoassign.AutoAssignChecker;
|
||||||
|
import org.eclipse.hawkbit.repository.model.Action.ActionType;
|
||||||
|
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
||||||
|
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||||
|
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
||||||
|
import org.eclipse.hawkbit.repository.model.Target;
|
||||||
|
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
|
||||||
|
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feature: Component Tests - Access Control<br/>
|
||||||
|
* Story: Test Target Access Controller
|
||||||
|
*/
|
||||||
|
@ContextConfiguration(classes = { DefaultAccessControllerConfiguration.class })
|
||||||
|
class TargetAccessControllerTest extends AbstractJpaIntegrationTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
AutoAssignChecker autoAssignChecker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies read access rules for targets
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void verifyTargetReadOperations() {
|
||||||
|
final Target permittedTarget = targetManagement
|
||||||
|
.create(entityFactory.target().create().controllerId("device01").status(TargetUpdateStatus.REGISTERED));
|
||||||
|
|
||||||
|
final Target hiddenTarget = targetManagement
|
||||||
|
.create(entityFactory.target().create().controllerId("device02").status(TargetUpdateStatus.REGISTERED));
|
||||||
|
|
||||||
|
runAs(withUser("user", READ_TARGET + "/controllerId==" + permittedTarget.getControllerId()), () -> {
|
||||||
|
// verify targetManagement#findAll
|
||||||
|
assertThat(targetManagement.findAll(Pageable.unpaged()).get().map(Identifiable::getId).toList())
|
||||||
|
.containsOnly(permittedTarget.getId());
|
||||||
|
|
||||||
|
// verify targetManagement#findByRsql
|
||||||
|
assertThat(targetManagement.findByRsql("id==*", Pageable.unpaged()).get().map(Identifiable::getId).toList())
|
||||||
|
.containsOnly(permittedTarget.getId());
|
||||||
|
|
||||||
|
// verify targetManagement#findByUpdateStatus
|
||||||
|
assertThat(targetManagement.findByUpdateStatus(TargetUpdateStatus.REGISTERED, Pageable.unpaged()).get()
|
||||||
|
.map(Identifiable::getId).toList()).containsOnly(permittedTarget.getId());
|
||||||
|
|
||||||
|
// verify targetManagement#getByControllerID
|
||||||
|
assertThat(targetManagement.getByControllerID(permittedTarget.getControllerId())).isPresent();
|
||||||
|
final String hiddenTargetControllerId = hiddenTarget.getControllerId();
|
||||||
|
assertThatThrownBy(() -> targetManagement.getByControllerID(hiddenTargetControllerId))
|
||||||
|
.as("Missing read permissions for hidden target.")
|
||||||
|
.isInstanceOf(InsufficientPermissionException.class);
|
||||||
|
|
||||||
|
// verify targetManagement#getByControllerID
|
||||||
|
assertThat(targetManagement
|
||||||
|
.getByControllerID(Arrays.asList(permittedTarget.getControllerId(), hiddenTargetControllerId))
|
||||||
|
.stream().map(Identifiable::getId).toList()).containsOnly(permittedTarget.getId());
|
||||||
|
|
||||||
|
// verify targetManagement#get
|
||||||
|
assertThat(targetManagement.get(permittedTarget.getId())).isPresent();
|
||||||
|
assertThat(targetManagement.get(hiddenTarget.getId())).isEmpty();
|
||||||
|
|
||||||
|
// verify targetManagement#get
|
||||||
|
assertThat(targetManagement.get(Arrays.asList(permittedTarget.getId(), hiddenTarget.getId())).stream()
|
||||||
|
.map(Identifiable::getId).toList()).containsOnly(permittedTarget.getId());
|
||||||
|
|
||||||
|
// verify targetManagement#getControllerAttributes
|
||||||
|
assertThat(targetManagement.getControllerAttributes(permittedTarget.getControllerId())).isEmpty();
|
||||||
|
assertThatThrownBy(() -> targetManagement.getControllerAttributes(hiddenTargetControllerId))
|
||||||
|
.as("Target should not be found.")
|
||||||
|
.isInstanceOf(InsufficientPermissionException.class);
|
||||||
|
});
|
||||||
|
|
||||||
|
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement
|
||||||
|
.create(entityFactory.targetFilterQuery().create().name("test").query("id==*"));
|
||||||
|
|
||||||
|
runAs(withUser("user", READ_TARGET + "/controllerId==" + permittedTarget.getControllerId()), () -> {
|
||||||
|
// verify targetManagement#findByTargetFilterQuery
|
||||||
|
assertThat(targetManagement.findByTargetFilterQuery(targetFilterQuery.getId(), Pageable.unpaged()).get()
|
||||||
|
.map(Identifiable::getId).toList()).containsOnly(permittedTarget.getId());
|
||||||
|
|
||||||
|
// verify targetManagement#findByTargetFilterQuery (used by UI)
|
||||||
|
assertThat(targetManagement.findByFilters(new FilterParams(null, null, null, null), Pageable.unpaged()).get()
|
||||||
|
.map(Identifiable::getId).toList()).containsOnly(permittedTarget.getId());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void verifyTagFilteringAndManagement() {
|
||||||
|
final Target permittedTarget = targetManagement
|
||||||
|
.create(entityFactory.target().create().controllerId("device01").status(TargetUpdateStatus.REGISTERED));
|
||||||
|
|
||||||
|
final Target readOnlyTarget = targetManagement
|
||||||
|
.create(entityFactory.target().create().controllerId("device02").status(TargetUpdateStatus.REGISTERED));
|
||||||
|
final String readOnlyTargetControllerId = readOnlyTarget.getControllerId();
|
||||||
|
|
||||||
|
final Target hiddenTarget = targetManagement
|
||||||
|
.create(entityFactory.target().create().controllerId("device03").status(TargetUpdateStatus.REGISTERED));
|
||||||
|
|
||||||
|
final Long myTagId = targetTagManagement.create(entityFactory.tag().create().name("myTag")).getId();
|
||||||
|
|
||||||
|
// perform tag assignment before setting access rules
|
||||||
|
targetManagement.assignTag(
|
||||||
|
Arrays.asList(permittedTarget.getControllerId(), readOnlyTargetControllerId, hiddenTarget.getControllerId()), myTagId);
|
||||||
|
|
||||||
|
runAs(withUser("user",
|
||||||
|
READ_TARGET + "/controllerId==" + permittedTarget.getControllerId() + " or controllerId==" + readOnlyTargetControllerId,
|
||||||
|
UPDATE_TARGET + "/controllerId==" + permittedTarget.getControllerId(),
|
||||||
|
READ_REPOSITORY),
|
||||||
|
() -> {
|
||||||
|
// verify targetManagement#findByTag
|
||||||
|
assertThat(
|
||||||
|
targetManagement.findByTag(myTagId, Pageable.unpaged()).get().map(Identifiable::getId).toList())
|
||||||
|
.containsOnly(permittedTarget.getId(), readOnlyTarget.getId());
|
||||||
|
|
||||||
|
// verify targetManagement#findByRsqlAndTag
|
||||||
|
assertThat(targetManagement.findByRsqlAndTag("id==*", myTagId, Pageable.unpaged()).get()
|
||||||
|
.map(Identifiable::getId).toList()).containsOnly(permittedTarget.getId(), readOnlyTarget.getId());
|
||||||
|
|
||||||
|
// verify targetManagement#assignTag on permitted target
|
||||||
|
assertThat(targetManagement.assignTag(Collections.singletonList(permittedTarget.getControllerId()), myTagId)).hasSize(1);
|
||||||
|
// verify targetManagement#unassignTag on permitted target
|
||||||
|
assertThat(targetManagement.unassignTag(Collections.singletonList(permittedTarget.getControllerId()), myTagId)).hasSize(1);
|
||||||
|
// verify targetManagement#assignTag on permitted target
|
||||||
|
assertThat(targetManagement.assignTag(Collections.singletonList(permittedTarget.getControllerId()), myTagId))
|
||||||
|
.hasSize(1);
|
||||||
|
// verify targetManagement#unAssignTag on permitted target
|
||||||
|
assertThat(targetManagement.unassignTag(List.of(permittedTarget.getControllerId()), myTagId).get(0).getControllerId())
|
||||||
|
.isEqualTo(permittedTarget.getControllerId());
|
||||||
|
|
||||||
|
// assignment is denied for readOnlyTarget (read, but no update permissions)
|
||||||
|
final List<String> readTargetControllerIdList = Collections.singletonList(readOnlyTargetControllerId);
|
||||||
|
assertThatThrownBy(() -> targetManagement.assignTag(readTargetControllerIdList, myTagId))
|
||||||
|
.as("Missing update permissions for target to toggle tag assignment.")
|
||||||
|
.isInstanceOfAny(InsufficientPermissionException.class);
|
||||||
|
|
||||||
|
// assignment is denied for readOnlyTarget (read, but no update permissions)
|
||||||
|
final List<String> readOnlyTargetControllerIdList = List.of(readOnlyTargetControllerId);
|
||||||
|
assertThatThrownBy(() -> targetManagement.unassignTag(readOnlyTargetControllerIdList, myTagId))
|
||||||
|
.as("Missing update permissions for target to toggle tag assignment.")
|
||||||
|
.isInstanceOf(InsufficientPermissionException.class);
|
||||||
|
|
||||||
|
// assignment is denied for hiddenTarget since it's hidden
|
||||||
|
final List<String> hiddenTargetControllerIdList = Collections.singletonList(hiddenTarget.getControllerId());
|
||||||
|
assertThatThrownBy(() -> targetManagement.assignTag(hiddenTargetControllerIdList, myTagId))
|
||||||
|
.as("Missing update permissions for target to toggle tag assignment.")
|
||||||
|
.isInstanceOf(InsufficientPermissionException.class);
|
||||||
|
|
||||||
|
// assignment is denied for hiddenTarget since it's hidden
|
||||||
|
assertThatThrownBy(() -> targetManagement.assignTag(hiddenTargetControllerIdList, myTagId))
|
||||||
|
.as("Missing update permissions for target to toggle tag assignment.")
|
||||||
|
.isInstanceOf(InsufficientPermissionException.class);
|
||||||
|
|
||||||
|
// assignment is denied for hiddenTarget since it's hidden
|
||||||
|
assertThatThrownBy(() -> targetManagement.unassignTag(hiddenTargetControllerIdList, myTagId))
|
||||||
|
.as("Missing update permissions for target to toggle tag assignment.")
|
||||||
|
.isInstanceOf(InsufficientPermissionException.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies rules for target assignment
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void verifyTargetAssignment() {
|
||||||
|
final Long dsId = testdataFactory.createDistributionSet("myDs").getId();
|
||||||
|
distributionSetManagement.lock(dsId);
|
||||||
|
|
||||||
|
final Target permittedTarget = targetManagement
|
||||||
|
.create(entityFactory.target().create().controllerId("device01").status(TargetUpdateStatus.REGISTERED));
|
||||||
|
final String hiddenTargetControllerId = targetManagement
|
||||||
|
.create(entityFactory.target().create().controllerId("device02").status(TargetUpdateStatus.REGISTERED))
|
||||||
|
.getControllerId();
|
||||||
|
|
||||||
|
runAs(withUser("user", READ_TARGET + "/controllerId==" + permittedTarget.getControllerId()), () ->
|
||||||
|
// verify targetManagement#findByUpdateStatus before assignment
|
||||||
|
assertThat(targetManagement.findByUpdateStatus(TargetUpdateStatus.REGISTERED, Pageable.unpaged()).get()
|
||||||
|
.map(Identifiable::getId).toList()).containsOnly(permittedTarget.getId()));
|
||||||
|
|
||||||
|
runAs(withUser("user",
|
||||||
|
READ_TARGET + "/controllerId==" + permittedTarget.getControllerId(),
|
||||||
|
UPDATE_TARGET + "/controllerId==" + permittedTarget.getControllerId(),
|
||||||
|
READ_REPOSITORY), () -> {
|
||||||
|
assertThat(assignDistributionSet(dsId, permittedTarget.getControllerId()).getAssigned()).isEqualTo(1);
|
||||||
|
// assigning of not allowed target behaves as not found
|
||||||
|
assertThatThrownBy(() -> assignDistributionSet(dsId, hiddenTargetControllerId)).isInstanceOf(AssertionError.class);
|
||||||
|
|
||||||
|
// verify targetManagement#findByUpdateStatus(REGISTERED) after assignment
|
||||||
|
assertThat(targetManagement.findByUpdateStatus(TargetUpdateStatus.REGISTERED, Pageable.unpaged())
|
||||||
|
.getTotalElements()).isZero();
|
||||||
|
|
||||||
|
// verify targetManagement#findByUpdateStatus(PENDING) after assignment
|
||||||
|
assertThat(targetManagement.findByUpdateStatus(TargetUpdateStatus.PENDING, Pageable.unpaged()).get()
|
||||||
|
.map(Identifiable::getId).toList()).containsOnly(permittedTarget.getId());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies rules for target assignment
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void verifyTargetAssignmentOnNonUpdatableTarget() {
|
||||||
|
final Long firstDsId = testdataFactory.createDistributionSet("myDs").getId();
|
||||||
|
distributionSetManagement.lock(firstDsId);
|
||||||
|
final DistributionSet secondDs = testdataFactory.createDistributionSet("anotherDs");
|
||||||
|
distributionSetManagement.lock(secondDs.getId());
|
||||||
|
|
||||||
|
final Target manageableTarget = targetManagement
|
||||||
|
.create(entityFactory.target().create().controllerId("device01").status(TargetUpdateStatus.REGISTERED));
|
||||||
|
final Target readOnlyTarget = targetManagement
|
||||||
|
.create(entityFactory.target().create().controllerId("device02").status(TargetUpdateStatus.REGISTERED));
|
||||||
|
|
||||||
|
runAs(withUser("user",
|
||||||
|
READ_TARGET + "/controllerId==" + manageableTarget.getControllerId() + " or controllerId==" + readOnlyTarget.getControllerId(),
|
||||||
|
UPDATE_TARGET + "/controllerId==" + manageableTarget.getControllerId(),
|
||||||
|
READ_REPOSITORY), () -> {
|
||||||
|
// assignment is permitted for manageableTarget
|
||||||
|
assertThat(assignDistributionSet(firstDsId, manageableTarget.getControllerId()).getAssigned()).isEqualTo(1);
|
||||||
|
|
||||||
|
// assignment is denied for readOnlyTarget (read, but no update permissions)
|
||||||
|
final var readOnlyTargetControllerId = readOnlyTarget.getControllerId();
|
||||||
|
assertThatThrownBy(() -> assignDistributionSet(firstDsId, readOnlyTargetControllerId)).isInstanceOf(AssertionError.class);
|
||||||
|
|
||||||
|
// bunch assignment skips denied since at least one target without update permissions is present
|
||||||
|
assertThat(assignDistributionSet(secondDs.getId(),
|
||||||
|
Arrays.asList(readOnlyTargetControllerId, manageableTarget.getControllerId()),
|
||||||
|
ActionType.FORCED).getAssigned()).isEqualTo(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies only manageable targets are part of the rollout
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void verifyRolloutTargetScope() {
|
||||||
|
final DistributionSet ds = testdataFactory.createDistributionSet("myDs");
|
||||||
|
distributionSetManagement.lock(ds.getId());
|
||||||
|
|
||||||
|
final String[] updateTargetControllerIds = { "update1", "update2", "update3" };
|
||||||
|
final List<Target> updateTargets = testdataFactory.createTargets(updateTargetControllerIds);
|
||||||
|
final String[] readTargetControllerIds = { "read1", "read2", "read3", "read4" };
|
||||||
|
final List<Target> readTargets = testdataFactory.createTargets(readTargetControllerIds);
|
||||||
|
final List<Target> hiddenTargets = testdataFactory.createTargets("hidden1", "hidden2", "hidden3", "hidden4", "hidden5");
|
||||||
|
|
||||||
|
runAs(withUser("user",
|
||||||
|
READ_TARGET + "/controllerId=in=(" + String.join(", ", List.of(updateTargetControllerIds)) + ")" +
|
||||||
|
" or controllerId=in=(" + String.join(", ", List.of(readTargetControllerIds)) + ")",
|
||||||
|
UPDATE_TARGET + "/controllerId=in=(" + String.join(", ", List.of(updateTargetControllerIds)) + ")",
|
||||||
|
READ_REPOSITORY,
|
||||||
|
CREATE_ROLLOUT, READ_ROLLOUT), () -> {
|
||||||
|
final Rollout rollout = testdataFactory.createRolloutByVariables(
|
||||||
|
"testRollout", "description", updateTargets.size(), "id==*", ds, "50", "5");
|
||||||
|
assertThat(rollout.getTotalTargets()).isEqualTo(updateTargets.size());
|
||||||
|
|
||||||
|
final List<RolloutGroup> content = rolloutGroupManagement.findByRollout(rollout.getId(), Pageable.unpaged()).getContent();
|
||||||
|
assertThat(content).hasSize(updateTargets.size());
|
||||||
|
|
||||||
|
final List<Target> rolloutTargets = content.stream().flatMap(
|
||||||
|
group -> rolloutGroupManagement.findTargetsOfRolloutGroup(group.getId(), Pageable.unpaged()).get())
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
assertThat(rolloutTargets).hasSize(updateTargets.size()).allMatch(
|
||||||
|
target -> updateTargets.stream().anyMatch(readTarget -> readTarget.getId().equals(target.getId())))
|
||||||
|
.noneMatch(target -> readTargets.stream()
|
||||||
|
.anyMatch(readTarget -> readTarget.getId().equals(target.getId())))
|
||||||
|
.noneMatch(target -> hiddenTargets.stream()
|
||||||
|
.anyMatch(readTarget -> readTarget.getId().equals(target.getId())));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies only manageable targets are part of an auto assignment.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void verifyAutoAssignmentTargetScope() {
|
||||||
|
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
|
||||||
|
distributionSetManagement.lock(distributionSet.getId());
|
||||||
|
|
||||||
|
final String[] updateTargetControllerIds = { "update1", "update2", "update3" };
|
||||||
|
final List<Target> updateTargets = testdataFactory.createTargets(updateTargetControllerIds);
|
||||||
|
final String[] readTargetControllerIds = { "read1", "read2", "read3", "read4" };
|
||||||
|
final List<Target> readTargets = testdataFactory.createTargets(readTargetControllerIds);
|
||||||
|
final List<Target> hiddenTargets = testdataFactory.createTargets("hidden1", "hidden2", "hidden3", "hidden4", "hidden5");
|
||||||
|
|
||||||
|
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement
|
||||||
|
.create(entityFactory.targetFilterQuery().create().name("testName").query("id==*"));
|
||||||
|
|
||||||
|
runAs(withUser("user",
|
||||||
|
READ_TARGET + "/controllerId=in=(" + String.join(", ", List.of(updateTargetControllerIds)) + ")" +
|
||||||
|
" or controllerId=in=(" + String.join(", ", List.of(readTargetControllerIds)) + ")",
|
||||||
|
UPDATE_TARGET + "/controllerId=in=(" + String.join(", ", List.of(updateTargetControllerIds)) + ")",
|
||||||
|
READ_REPOSITORY + "/id==" + distributionSet.getId()), () -> {
|
||||||
|
|
||||||
|
targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery()
|
||||||
|
.updateAutoAssign(targetFilterQuery.getId()).ds(distributionSet.getId()));
|
||||||
|
|
||||||
|
autoAssignChecker.checkAllTargets();
|
||||||
|
|
||||||
|
assertThat(targetManagement.findByAssignedDistributionSet(distributionSet.getId(), Pageable.unpaged())
|
||||||
|
.getContent())
|
||||||
|
.hasSize(updateTargets.size())
|
||||||
|
.allMatch(assignedTarget -> updateTargets.stream()
|
||||||
|
.anyMatch(updateTarget -> updateTarget.getId().equals(assignedTarget.getId())))
|
||||||
|
.noneMatch(assignedTarget -> readTargets.stream()
|
||||||
|
.anyMatch(updateTarget -> updateTarget.getId().equals(assignedTarget.getId())))
|
||||||
|
.noneMatch(assignedTarget -> hiddenTargets.stream()
|
||||||
|
.anyMatch(updateTarget -> updateTarget.getId().equals(assignedTarget.getId())));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2023 Bosch.IO GmbH and others
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made
|
||||||
|
* available under the terms of the Eclipse Public License 2.0
|
||||||
|
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.eclipse.hawkbit.repository.jpa.acm;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.eclipse.hawkbit.im.authentication.SpPermission.DELETE_TARGET;
|
||||||
|
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_TARGET;
|
||||||
|
import static org.eclipse.hawkbit.im.authentication.SpPermission.UPDATE_TARGET;
|
||||||
|
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.runAs;
|
||||||
|
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.withUser;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.eclipse.hawkbit.repository.Identifiable;
|
||||||
|
import org.eclipse.hawkbit.repository.builder.TargetTypeCreate;
|
||||||
|
import org.eclipse.hawkbit.repository.builder.TargetTypeUpdate;
|
||||||
|
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
||||||
|
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.specifications.TargetTypeSpecification;
|
||||||
|
import org.eclipse.hawkbit.repository.model.TargetType;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Feature: Component Tests - Access Control<br/>
|
||||||
|
* Story: Test Target Type Access Controller
|
||||||
|
*/
|
||||||
|
@ContextConfiguration(classes = { DefaultAccessControllerConfiguration.class })
|
||||||
|
class TargetTypeAccessControllerTest extends AbstractJpaIntegrationTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies read access rules for target types
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void verifyTargetTypeReadOperations() {
|
||||||
|
final TargetType permittedTargetType = targetTypeManagement.create(entityFactory.targetType().create().name("type1"));
|
||||||
|
final TargetType hiddenTargetType = targetTypeManagement.create(entityFactory.targetType().create().name("type2"));
|
||||||
|
|
||||||
|
runAs(withUser("user", READ_TARGET + "/id==" + permittedTargetType.getId()), () -> {
|
||||||
|
// verify targetTypeManagement#findAll
|
||||||
|
assertThat(targetTypeManagement.findAll(Pageable.unpaged()).get().map(Identifiable::getId).toList())
|
||||||
|
.containsOnly(permittedTargetType.getId());
|
||||||
|
|
||||||
|
// verify targetTypeManagement#findByRsql
|
||||||
|
assertThat(targetTypeManagement.findByRsql("name==*", Pageable.unpaged()).get().map(Identifiable::getId).toList())
|
||||||
|
.containsOnly(permittedTargetType.getId());
|
||||||
|
|
||||||
|
// verify targetTypeManagement#findByName
|
||||||
|
assertThat(targetTypeManagement.findByName(permittedTargetType.getName(), Pageable.unpaged()).getContent())
|
||||||
|
.hasSize(1).satisfies(results ->
|
||||||
|
assertThat(results.get(0).getId()).isEqualTo(permittedTargetType.getId()));
|
||||||
|
assertThat(targetTypeManagement.findByName(hiddenTargetType.getName(), Pageable.unpaged())).isEmpty();
|
||||||
|
|
||||||
|
// verify targetTypeManagement#count
|
||||||
|
assertThat(targetTypeManagement.count()).isEqualTo(1);
|
||||||
|
|
||||||
|
// verify targetTypeManagement#countByName
|
||||||
|
assertThat(targetTypeManagement.countByName(permittedTargetType.getName())).isEqualTo(1);
|
||||||
|
assertThat(targetTypeManagement.countByName(hiddenTargetType.getName())).isZero();
|
||||||
|
|
||||||
|
// verify targetTypeManagement#countByName
|
||||||
|
assertThat(targetTypeManagement.countByName(permittedTargetType.getName())).isEqualTo(1);
|
||||||
|
assertThat(targetTypeManagement.countByName(hiddenTargetType.getName())).isZero();
|
||||||
|
|
||||||
|
// verify targetTypeManagement#get by id
|
||||||
|
assertThat(targetTypeManagement.get(permittedTargetType.getId())).isPresent();
|
||||||
|
final Long hiddenTargetTypeId = hiddenTargetType.getId();
|
||||||
|
assertThat(targetTypeManagement.get(hiddenTargetTypeId)).isEmpty();
|
||||||
|
|
||||||
|
// verify targetTypeManagement#getByName
|
||||||
|
assertThat(targetTypeManagement.getByName(permittedTargetType.getName())).isPresent();
|
||||||
|
assertThat(targetTypeManagement.getByName(hiddenTargetType.getName())).isEmpty();
|
||||||
|
|
||||||
|
// verify targetTypeManagement#get by ids
|
||||||
|
assertThat(targetTypeManagement.get(Arrays.asList(permittedTargetType.getId(), hiddenTargetTypeId))
|
||||||
|
.stream().map(Identifiable::getId).toList()).containsOnly(permittedTargetType.getId());
|
||||||
|
|
||||||
|
// verify targetTypeManagement#update is not possible. Assert exception thrown.
|
||||||
|
final TargetTypeUpdate targetTypeUpdate = entityFactory.targetType().update(hiddenTargetTypeId)
|
||||||
|
.name(hiddenTargetType.getName() + "/new").description("newDesc");
|
||||||
|
assertThatThrownBy(() -> targetTypeManagement.update(targetTypeUpdate))
|
||||||
|
.as("Target type update shouldn't be allowed since the target type is not visible.")
|
||||||
|
.isInstanceOf(InsufficientPermissionException.class);
|
||||||
|
|
||||||
|
// verify targetTypeManagement#delete is not possible. Assert exception thrown.
|
||||||
|
assertThatThrownBy(() -> targetTypeManagement.delete(hiddenTargetTypeId))
|
||||||
|
.as("Target type delete shouldn't be allowed since the target type is not visible.")
|
||||||
|
.isInstanceOf(InsufficientPermissionException.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies delete access rules for target types
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void verifyTargetTypeDeleteOperations() {
|
||||||
|
final TargetType manageableTargetType = targetTypeManagement.create(entityFactory.targetType().create().name("type1"));
|
||||||
|
final TargetType readOnlyTargetType = targetTypeManagement.create(entityFactory.targetType().create().name("type2"));
|
||||||
|
|
||||||
|
runAs(withUser("user",
|
||||||
|
READ_TARGET + "/id==" + manageableTargetType.getId() + " or id==" + readOnlyTargetType.getId(),
|
||||||
|
DELETE_TARGET + "/id==" + manageableTargetType.getId()), () -> {
|
||||||
|
// delete the manageableTargetType
|
||||||
|
targetTypeManagement.delete(manageableTargetType.getId());
|
||||||
|
|
||||||
|
// verify targetTypeManagement#delete for readOnlyTargetType is not possible
|
||||||
|
final Long readOnlyTargetTypeId = readOnlyTargetType.getId();
|
||||||
|
assertThatThrownBy(() -> targetTypeManagement.delete(readOnlyTargetTypeId))
|
||||||
|
.isInstanceOfAny(InsufficientPermissionException.class, EntityNotFoundException.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies update operation for target types
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void verifyTargetTypeUpdateOperations() {
|
||||||
|
final TargetType manageableTargetType = targetTypeManagement.create(entityFactory.targetType().create().name("type1"));
|
||||||
|
final TargetType readOnlyTargetType = targetTypeManagement.create(entityFactory.targetType().create().name("type2"));
|
||||||
|
|
||||||
|
runAs(withUser("user",
|
||||||
|
READ_TARGET + "/id==" + manageableTargetType.getId() + " or id==" + readOnlyTargetType.getId(),
|
||||||
|
UPDATE_TARGET + "/id==" + manageableTargetType.getId()), () -> {
|
||||||
|
// update the manageableTargetType
|
||||||
|
targetTypeManagement.update(entityFactory.targetType().update(manageableTargetType.getId())
|
||||||
|
.name(manageableTargetType.getName() + "/new").description("newDesc"));
|
||||||
|
|
||||||
|
// verify targetTypeManagement#update for readOnlyTargetType is not possible
|
||||||
|
final TargetTypeUpdate targetTypeUpdate = entityFactory.targetType().update(readOnlyTargetType.getId())
|
||||||
|
.name(readOnlyTargetType.getName() + "/new").description("newDesc");
|
||||||
|
assertThatThrownBy(() -> targetTypeManagement.update(targetTypeUpdate))
|
||||||
|
.isInstanceOf(InsufficientPermissionException.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies create operation blocked by controller
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void verifyTargetTypeCreationBlockedByAccessController() {
|
||||||
|
runAs(withUser("user", READ_TARGET, UPDATE_TARGET), () -> {
|
||||||
|
// verify targetTypeManagement#create for any type
|
||||||
|
final TargetTypeCreate targetTypeCreate = entityFactory.targetType().create().name("type1");
|
||||||
|
assertThatThrownBy(() -> targetTypeManagement.create(targetTypeCreate))
|
||||||
|
.as("Target type create shouldn't be allowed since the target type is not visible.")
|
||||||
|
.isInstanceOf(InsufficientPermissionException.class);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2023 Bosch.IO GmbH and others
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made
|
|
||||||
* available under the terms of the Eclipse Public License 2.0
|
|
||||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.eclipse.hawkbit.repository.jpa.acm.controller;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import org.eclipse.hawkbit.ContextAware;
|
|
||||||
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType;
|
|
||||||
import org.eclipse.hawkbit.security.SecurityContextTenantAware;
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.data.jpa.domain.Specification;
|
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
|
||||||
|
|
||||||
@ContextConfiguration(classes = { AbstractAccessControllerTest.AccessControlTestConfig.class })
|
|
||||||
public abstract class AbstractAccessControllerTest extends AbstractJpaIntegrationTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
protected TestAccessControlManger testAccessControlManger;
|
|
||||||
|
|
||||||
protected static <T> List<T> merge(final List<T> lists0, final List<T> list1) {
|
|
||||||
final List<T> merge = new ArrayList<>(lists0);
|
|
||||||
merge.addAll(list1);
|
|
||||||
return merge;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void permitAllOperations(final AccessController.Operation operation) {
|
|
||||||
testAccessControlManger.defineAccessRule(JpaTarget.class, operation, Specification.where(null), type -> true);
|
|
||||||
testAccessControlManger.defineAccessRule(JpaTargetType.class, operation, Specification.where(null), type -> true);
|
|
||||||
testAccessControlManger.defineAccessRule(JpaDistributionSet.class, operation, Specification.where(null), type -> true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
void beforeEach() {
|
|
||||||
testAccessControlManger.deleteAllRules();
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterEach
|
|
||||||
void afterEach() {
|
|
||||||
testAccessControlManger.deleteAllRules();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class AccessControlTestConfig {
|
|
||||||
|
|
||||||
private final ContextAware contextAware = new SecurityContextTenantAware((tenant, username) -> List.of());
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public ContextAware contextAware() {
|
|
||||||
return contextAware;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public TestAccessControlManger accessControlTestManger() {
|
|
||||||
return new TestAccessControlManger();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public AccessController<JpaTarget> targetAccessController(final TestAccessControlManger testAccessControlManger) {
|
|
||||||
return new AccessController<>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Specification<JpaTarget>> getAccessRules(final Operation operation) {
|
|
||||||
if (contextAware.getCurrentTenant() != null
|
|
||||||
&& SecurityContextTenantAware.SYSTEM_USER.equals(contextAware.getCurrentUsername())) {
|
|
||||||
// as tenant, no restrictions
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Optional.ofNullable(testAccessControlManger.getAccessRule(JpaTarget.class, operation));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void assertOperationAllowed(final Operation operation, final JpaTarget entity) throws InsufficientPermissionException {
|
|
||||||
testAccessControlManger.assertOperation(JpaTarget.class, operation, List.of(entity));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return AccessController.class.getSimpleName() + '<' + JpaTarget.class.getSimpleName() + '>';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public AccessController<JpaTargetType> targetTypeAccessController(final TestAccessControlManger testAccessControlManger) {
|
|
||||||
return new AccessController<>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Specification<JpaTargetType>> getAccessRules(final Operation operation) {
|
|
||||||
if (contextAware.getCurrentTenant() != null
|
|
||||||
&& SecurityContextTenantAware.SYSTEM_USER.equals(contextAware.getCurrentUsername())) {
|
|
||||||
// as tenant, no restrictions
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Optional.ofNullable(testAccessControlManger.getAccessRule(JpaTargetType.class, operation));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void assertOperationAllowed(final Operation operation, final JpaTargetType entity)
|
|
||||||
throws InsufficientPermissionException {
|
|
||||||
testAccessControlManger.assertOperation(JpaTargetType.class, operation, List.of(entity));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return AccessController.class.getSimpleName() + '<' + JpaTargetType.class.getSimpleName() + '>';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public AccessController<JpaDistributionSet> distributionSetAccessController(final TestAccessControlManger testAccessControlManger) {
|
|
||||||
return new AccessController<>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Optional<Specification<JpaDistributionSet>> getAccessRules(final Operation operation) {
|
|
||||||
if (contextAware.getCurrentTenant() != null
|
|
||||||
&& SecurityContextTenantAware.SYSTEM_USER.equals(contextAware.getCurrentUsername())) {
|
|
||||||
// as tenant, no restrictions
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Optional.ofNullable(testAccessControlManger.getAccessRule(JpaDistributionSet.class, operation));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void assertOperationAllowed(final Operation operation, final JpaDistributionSet entity)
|
|
||||||
throws InsufficientPermissionException {
|
|
||||||
testAccessControlManger.assertOperation(JpaDistributionSet.class, operation, List.of(entity));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return AccessController.class.getSimpleName() + '<' + JpaDistributionSet.class.getSimpleName() + '>';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,327 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2023 Bosch.IO GmbH and others
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made
|
|
||||||
* available under the terms of the Eclipse Public License 2.0
|
|
||||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.eclipse.hawkbit.repository.jpa.acm.controller;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import jakarta.persistence.criteria.Predicate;
|
|
||||||
|
|
||||||
import org.eclipse.hawkbit.repository.Identifiable;
|
|
||||||
import org.eclipse.hawkbit.repository.builder.AutoAssignDistributionSetUpdate;
|
|
||||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
|
||||||
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
|
|
||||||
import org.eclipse.hawkbit.repository.model.Action;
|
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSetFilter;
|
|
||||||
import org.eclipse.hawkbit.repository.model.SoftwareModule;
|
|
||||||
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.data.domain.Pageable;
|
|
||||||
import org.springframework.data.jpa.domain.Specification;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Feature: Component Tests - Access Control<br/>
|
|
||||||
* Story: Test Distribution Set Access Controller
|
|
||||||
*/
|
|
||||||
class DistributionSetAccessControllerTest extends AbstractAccessControllerTest {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies read access rules for distribution sets
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void verifyDistributionSetReadOperations() {
|
|
||||||
permitAllOperations(AccessController.Operation.READ);
|
|
||||||
permitAllOperations(AccessController.Operation.CREATE);
|
|
||||||
permitAllOperations(AccessController.Operation.UPDATE);
|
|
||||||
|
|
||||||
final DistributionSet permitted = testdataFactory.createDistributionSet();
|
|
||||||
final DistributionSet hidden = testdataFactory.createDistributionSet();
|
|
||||||
|
|
||||||
final Action permittedAction = testdataFactory.performAssignment(permitted);
|
|
||||||
final Action hiddenAction = testdataFactory.performAssignment(hidden);
|
|
||||||
|
|
||||||
testAccessControlManger.deleteAllRules();
|
|
||||||
|
|
||||||
// define access controlling rule
|
|
||||||
defineAccess(AccessController.Operation.READ, permitted);
|
|
||||||
testAccessControlManger.defineAccessRule(
|
|
||||||
JpaTarget.class, AccessController.Operation.READ,
|
|
||||||
TargetSpecifications.hasId(permittedAction.getTarget().getId()),
|
|
||||||
target -> target.getId().equals(permittedAction.getTarget().getId()));
|
|
||||||
|
|
||||||
final Long permittedActionId = permitted.getId();
|
|
||||||
|
|
||||||
// verify distributionSetManagement#findAll
|
|
||||||
assertThat(distributionSetManagement.findAll(Pageable.unpaged()).get().map(Identifiable::getId).toList())
|
|
||||||
.containsOnly(permittedActionId);
|
|
||||||
|
|
||||||
// verify distributionSetManagement#findByRsql
|
|
||||||
assertThat(distributionSetManagement.findByRsql("name==*", Pageable.unpaged()).get().map(Identifiable::getId)
|
|
||||||
.toList()).containsOnly(permittedActionId);
|
|
||||||
|
|
||||||
// verify distributionSetManagement#findByCompleted
|
|
||||||
assertThat(distributionSetManagement.findByCompleted(true, Pageable.unpaged()).get().map(Identifiable::getId)
|
|
||||||
.toList()).containsOnly(permittedActionId);
|
|
||||||
|
|
||||||
// verify distributionSetManagement#findByDistributionSetFilter
|
|
||||||
assertThat(distributionSetManagement
|
|
||||||
.findByDistributionSetFilter(DistributionSetFilter.builder().isDeleted(false).build(), Pageable.unpaged())
|
|
||||||
.get().map(Identifiable::getId).toList()).containsOnly(permittedActionId);
|
|
||||||
|
|
||||||
// verify distributionSetManagement#get
|
|
||||||
assertThat(distributionSetManagement.get(permittedActionId)).isPresent();
|
|
||||||
final Long hiddenId = hidden.getId();
|
|
||||||
assertThat(distributionSetManagement.get(hiddenId)).isEmpty();
|
|
||||||
|
|
||||||
// verify distributionSetManagement#getWithDetails
|
|
||||||
assertThat(distributionSetManagement.getWithDetails(permittedActionId)).isPresent();
|
|
||||||
assertThat(distributionSetManagement.getWithDetails(hiddenId)).isEmpty();
|
|
||||||
|
|
||||||
// verify distributionSetManagement#get
|
|
||||||
assertThat(distributionSetManagement.getValid(permittedActionId).getId()).isEqualTo(permittedActionId);
|
|
||||||
assertThatThrownBy(() -> distributionSetManagement.getValid(hiddenId))
|
|
||||||
.as("Distribution set should not be found.").isInstanceOf(EntityNotFoundException.class);
|
|
||||||
|
|
||||||
// verify distributionSetManagement#get
|
|
||||||
final List<Long> allActionIds = Arrays.asList(permittedActionId, hiddenId);
|
|
||||||
assertThatThrownBy(() -> distributionSetManagement.get(allActionIds))
|
|
||||||
.as("Fail if request hidden.").isInstanceOf(EntityNotFoundException.class);
|
|
||||||
|
|
||||||
// verify distributionSetManagement#getByNameAndVersion
|
|
||||||
assertThat(distributionSetManagement.findByNameAndVersion(permitted.getName(), permitted.getVersion())).isPresent();
|
|
||||||
assertThat(distributionSetManagement.findByNameAndVersion(hidden.getName(), hidden.getVersion())).isEmpty();
|
|
||||||
|
|
||||||
// verify distributionSetManagement#getByAction
|
|
||||||
assertThat(distributionSetManagement.findByAction(permittedAction.getId())).isPresent();
|
|
||||||
final Long hiddenActionId = hiddenAction.getId();
|
|
||||||
assertThatThrownBy(() -> distributionSetManagement.findByAction(hiddenActionId))
|
|
||||||
.as("Action is hidden.").isInstanceOf(InsufficientPermissionException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies read access rules for distribution sets
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void verifyDistributionSetUpdates() {
|
|
||||||
// permit all operations first to prepare test setup
|
|
||||||
permitAllOperations(AccessController.Operation.READ);
|
|
||||||
permitAllOperations(AccessController.Operation.CREATE);
|
|
||||||
permitAllOperations(AccessController.Operation.UPDATE);
|
|
||||||
|
|
||||||
final DistributionSet permitted = testdataFactory.createDistributionSet();
|
|
||||||
final String mdPresetKey = "metadata.preset";
|
|
||||||
final String mdPresetValue = "presetValue";
|
|
||||||
distributionSetManagement.createMetadata(permitted.getId(), Map.of(mdPresetKey, mdPresetValue));
|
|
||||||
final DistributionSet readOnly = testdataFactory.createDistributionSet();
|
|
||||||
distributionSetManagement.createMetadata(readOnly.getId(), Map.of(mdPresetKey, mdPresetValue));
|
|
||||||
final DistributionSet hidden = testdataFactory.createDistributionSet();
|
|
||||||
distributionSetManagement.createMetadata(hidden.getId(), Map.of(mdPresetKey, mdPresetValue));
|
|
||||||
|
|
||||||
final SoftwareModule swModule = testdataFactory.createSoftwareModuleOs();
|
|
||||||
|
|
||||||
// entities created - reset rules
|
|
||||||
testAccessControlManger.deleteAllRules();
|
|
||||||
// define access controlling rule
|
|
||||||
defineAccess(AccessController.Operation.READ, permitted, readOnly);
|
|
||||||
defineAccess(AccessController.Operation.UPDATE, permitted);
|
|
||||||
|
|
||||||
// verify distributionSetManagement#assignSoftwareModules
|
|
||||||
final List<Long> singleModuleIdList = Collections.singletonList(swModule.getId());
|
|
||||||
assertThat(distributionSetManagement.assignSoftwareModules(permitted.getId(), singleModuleIdList))
|
|
||||||
.satisfies(ds -> assertThat(ds.getModules().stream().map(Identifiable::getId).toList()).contains(swModule.getId()));
|
|
||||||
final Long readOnlyId = readOnly.getId();
|
|
||||||
assertThatThrownBy(() -> distributionSetManagement.assignSoftwareModules(readOnlyId, singleModuleIdList))
|
|
||||||
.as("Distribution set not allowed to me modified.")
|
|
||||||
.isInstanceOf(InsufficientPermissionException.class);
|
|
||||||
final Long hiddenId = hidden.getId();
|
|
||||||
assertThatThrownBy(() -> distributionSetManagement.assignSoftwareModules(hiddenId, singleModuleIdList))
|
|
||||||
.as("Distribution set should not be visible.")
|
|
||||||
.isInstanceOf(EntityNotFoundException.class);
|
|
||||||
|
|
||||||
final Map<String, String> metadata = Map.of("test.create", mdPresetValue);
|
|
||||||
|
|
||||||
// verify distributionSetManagement#createMetaData
|
|
||||||
distributionSetManagement.createMetadata(permitted.getId(), metadata);
|
|
||||||
assertThatThrownBy(() -> distributionSetManagement.createMetadata(readOnlyId, metadata))
|
|
||||||
.as("Distribution set not allowed to be modified.")
|
|
||||||
.isInstanceOf(InsufficientPermissionException.class);
|
|
||||||
assertThatThrownBy(() -> distributionSetManagement.createMetadata(hiddenId, metadata))
|
|
||||||
.as("Distribution set should not be visible.")
|
|
||||||
.isInstanceOf(EntityNotFoundException.class);
|
|
||||||
|
|
||||||
// verify distributionSetManagement#updateMetaData
|
|
||||||
final String newValue = "newValue";
|
|
||||||
distributionSetManagement.updateMetadata(permitted.getId(), mdPresetKey, newValue);
|
|
||||||
assertThatThrownBy(() -> distributionSetManagement.updateMetadata(readOnlyId, mdPresetKey, newValue))
|
|
||||||
.as("Distribution set not allowed to me modified.")
|
|
||||||
.isInstanceOf(InsufficientPermissionException.class);
|
|
||||||
assertThatThrownBy(() -> distributionSetManagement.updateMetadata(hiddenId, mdPresetKey, newValue))
|
|
||||||
.as("Distribution set should not be visible.")
|
|
||||||
.isInstanceOf(EntityNotFoundException.class);
|
|
||||||
|
|
||||||
// verify distributionSetManagement#deleteMetaData
|
|
||||||
final String metadataKey = metadata.entrySet().stream().findAny().get().getKey();
|
|
||||||
distributionSetManagement.deleteMetadata(permitted.getId(), metadataKey);
|
|
||||||
assertThatThrownBy(() -> distributionSetManagement.deleteMetadata(readOnlyId, mdPresetKey))
|
|
||||||
.as("Distribution set not allowed to me modified.")
|
|
||||||
.isInstanceOf(InsufficientPermissionException.class);
|
|
||||||
assertThatThrownBy(() -> distributionSetManagement.deleteMetadata(hiddenId, mdPresetKey))
|
|
||||||
.as("Distribution set should not be visible.")
|
|
||||||
.isInstanceOf(EntityNotFoundException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void verifyTagFilteringAndManagement() {
|
|
||||||
// permit all operations first to prepare test setup
|
|
||||||
permitAllOperations(AccessController.Operation.READ);
|
|
||||||
permitAllOperations(AccessController.Operation.CREATE);
|
|
||||||
permitAllOperations(AccessController.Operation.UPDATE);
|
|
||||||
|
|
||||||
final DistributionSet permitted = testdataFactory.createDistributionSet();
|
|
||||||
final DistributionSet readOnly = testdataFactory.createDistributionSet();
|
|
||||||
final DistributionSet hidden = testdataFactory.createDistributionSet();
|
|
||||||
final Long dsTagId = distributionSetTagManagement.create(entityFactory.tag().create().name("dsTag")).getId();
|
|
||||||
final Long dsTag2Id = distributionSetTagManagement.create(entityFactory.tag().create().name("dsTag2")).getId();
|
|
||||||
|
|
||||||
// perform tag assignment before setting access rules
|
|
||||||
distributionSetManagement.assignTag(Arrays.asList(permitted.getId(), readOnly.getId(), hidden.getId()),
|
|
||||||
dsTagId);
|
|
||||||
// entities created - reset rules
|
|
||||||
testAccessControlManger.deleteAllRules();
|
|
||||||
|
|
||||||
// define access controlling rule
|
|
||||||
defineAccess(AccessController.Operation.READ, permitted, readOnly);
|
|
||||||
|
|
||||||
// allow updating the permitted distributionSet
|
|
||||||
defineAccess(AccessController.Operation.UPDATE, permitted);
|
|
||||||
|
|
||||||
assertThat(distributionSetManagement.findByTag(dsTagId, Pageable.unpaged()).get().map(Identifiable::getId)
|
|
||||||
.toList()).containsOnly(permitted.getId(), readOnly.getId());
|
|
||||||
|
|
||||||
assertThat(distributionSetManagement.findByRsqlAndTag("name==*", dsTagId, Pageable.unpaged()).get()
|
|
||||||
.map(Identifiable::getId).toList()).containsOnly(permitted.getId(), readOnly.getId());
|
|
||||||
|
|
||||||
// verify distributionSetManagement#unassignTag on permitted target
|
|
||||||
assertThat(distributionSetManagement
|
|
||||||
.unassignTag(Collections.singletonList(permitted.getId()), dsTagId))
|
|
||||||
.size()
|
|
||||||
.isEqualTo(1);
|
|
||||||
// verify distributionSetManagement#assignTag on permitted target
|
|
||||||
assertThat(distributionSetManagement.assignTag(Collections.singletonList(permitted.getId()), dsTagId))
|
|
||||||
.hasSize(1);
|
|
||||||
// verify distributionSetManagement#unAssignTag on permitted target
|
|
||||||
assertThat(distributionSetManagement.unassignTag(List.of(permitted.getId()), dsTagId)
|
|
||||||
.get(0).getId())
|
|
||||||
.isEqualTo(permitted.getId());
|
|
||||||
|
|
||||||
// assignment is denied for readOnlyTarget (read, but no update permissions)
|
|
||||||
final List<Long> readOblyList = Collections.singletonList(readOnly.getId());
|
|
||||||
assertThatThrownBy(() ->
|
|
||||||
distributionSetManagement.unassignTag(readOblyList, dsTagId))
|
|
||||||
.as("Missing update permissions for target to toggle tag assignment.")
|
|
||||||
.isInstanceOf(InsufficientPermissionException.class);
|
|
||||||
|
|
||||||
// assignment is denied for readOnlyTarget (read, but no update permissions)
|
|
||||||
// dsTag2- since - it is tagged with dsTag and won't do anything if assigning dsTag
|
|
||||||
assertThatThrownBy(() -> {
|
|
||||||
distributionSetManagement.assignTag(readOblyList, dsTag2Id);
|
|
||||||
}).as("Missing update permissions for target to toggle tag assignment.")
|
|
||||||
.isInstanceOf(InsufficientPermissionException.class);
|
|
||||||
|
|
||||||
// assignment is denied for hiddenTarget since it's hidden
|
|
||||||
final List<Long> hiddenList = Collections.singletonList(hidden.getId());
|
|
||||||
assertThatThrownBy(() -> distributionSetManagement.unassignTag(hiddenList, dsTagId))
|
|
||||||
.as("Missing update permissions for target to toggle tag assignment.")
|
|
||||||
.isInstanceOf(EntityNotFoundException.class);
|
|
||||||
|
|
||||||
// assignment is denied for hiddenTarget since it's hidden
|
|
||||||
assertThatThrownBy(() -> {
|
|
||||||
distributionSetManagement.assignTag(hiddenList, dsTagId);
|
|
||||||
}).as("Missing update permissions for target to toggle tag assignment.")
|
|
||||||
.isInstanceOf(EntityNotFoundException.class);
|
|
||||||
|
|
||||||
// assignment is denied for hiddenTarget since it's hidden
|
|
||||||
final List<Long> hiddenIdList = List.of(hidden.getId());
|
|
||||||
assertThatThrownBy(() -> distributionSetManagement.unassignTag(hiddenIdList, dsTagId))
|
|
||||||
.as("Missing update permissions for target to toggle tag assignment.")
|
|
||||||
.isInstanceOf(EntityNotFoundException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void verifyAutoAssignmentUsage() {
|
|
||||||
// permit all operations first to prepare test setup
|
|
||||||
permitAllOperations(AccessController.Operation.READ);
|
|
||||||
permitAllOperations(AccessController.Operation.CREATE);
|
|
||||||
permitAllOperations(AccessController.Operation.UPDATE);
|
|
||||||
|
|
||||||
final DistributionSet permitted = testdataFactory.createDistributionSet();
|
|
||||||
final DistributionSet readOnly = testdataFactory.createDistributionSet();
|
|
||||||
final DistributionSet hidden = testdataFactory.createDistributionSet();
|
|
||||||
// has to lock them, otherwise implicit lock shall be made which require DistributionSet update permissions
|
|
||||||
distributionSetManagement.lock(permitted.getId());
|
|
||||||
distributionSetManagement.lock(readOnly.getId());
|
|
||||||
distributionSetManagement.lock(hidden.getId());
|
|
||||||
|
|
||||||
// entities created - reset rules
|
|
||||||
testAccessControlManger.deleteAllRules();
|
|
||||||
// define read access
|
|
||||||
defineAccess(AccessController.Operation.READ, permitted, readOnly);
|
|
||||||
// permit update operation
|
|
||||||
defineAccess(AccessController.Operation.UPDATE, permitted);
|
|
||||||
|
|
||||||
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement
|
|
||||||
.create(entityFactory.targetFilterQuery().create().name("test").query("id==*"));
|
|
||||||
|
|
||||||
assertThat(targetFilterQueryManagement
|
|
||||||
.updateAutoAssignDS(new AutoAssignDistributionSetUpdate(targetFilterQuery.getId()).ds(permitted.getId())
|
|
||||||
.actionType(Action.ActionType.FORCED).confirmationRequired(false))
|
|
||||||
.getAutoAssignDistributionSet().getId()).isEqualTo(permitted.getId());
|
|
||||||
targetFilterQueryManagement
|
|
||||||
.updateAutoAssignDS(new AutoAssignDistributionSetUpdate(targetFilterQuery.getId())
|
|
||||||
.ds(readOnly.getId()).actionType(Action.ActionType.FORCED).confirmationRequired(false))
|
|
||||||
.getAutoAssignDistributionSet().getId();
|
|
||||||
final AutoAssignDistributionSetUpdate autoAssignDistributionSetUpdate = new AutoAssignDistributionSetUpdate(targetFilterQuery.getId())
|
|
||||||
.ds(hidden.getId()).actionType(Action.ActionType.FORCED).confirmationRequired(false);
|
|
||||||
assertThatThrownBy(() -> targetFilterQueryManagement.updateAutoAssignDS(autoAssignDistributionSetUpdate))
|
|
||||||
.isInstanceOf(EntityNotFoundException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void defineAccess(final AccessController.Operation operation, final DistributionSet... distributionSets) {
|
|
||||||
defineAccess(operation, List.of(distributionSets));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void defineAccess(final AccessController.Operation operation, final List<DistributionSet> targets) {
|
|
||||||
final List<Long> ids = targets.stream().map(DistributionSet::getId).toList();
|
|
||||||
testAccessControlManger.defineAccessRule(
|
|
||||||
JpaDistributionSet.class, operation,
|
|
||||||
dsByIds(ids),
|
|
||||||
distributionSet -> ids.contains(distributionSet.getId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Specification<JpaDistributionSet> dsByIds(final Collection<Long> distids) {
|
|
||||||
return (dsRoot, query, cb) -> {
|
|
||||||
final Predicate predicate = dsRoot.get(JpaDistributionSet_.id).in(distids);
|
|
||||||
query.distinct(true);
|
|
||||||
return predicate;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,418 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2023 Bosch.IO GmbH and others
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made
|
|
||||||
* available under the terms of the Eclipse Public License 2.0
|
|
||||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.eclipse.hawkbit.repository.jpa.acm.controller;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import jakarta.persistence.criteria.Predicate;
|
|
||||||
|
|
||||||
import org.eclipse.hawkbit.repository.FilterParams;
|
|
||||||
import org.eclipse.hawkbit.repository.Identifiable;
|
|
||||||
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.autoassign.AutoAssignChecker;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet_;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.specifications.TargetSpecifications;
|
|
||||||
import org.eclipse.hawkbit.repository.model.Action;
|
|
||||||
import org.eclipse.hawkbit.repository.model.DistributionSet;
|
|
||||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
|
||||||
import org.eclipse.hawkbit.repository.model.RolloutGroup;
|
|
||||||
import org.eclipse.hawkbit.repository.model.Target;
|
|
||||||
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
|
|
||||||
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.data.domain.Pageable;
|
|
||||||
import org.springframework.data.jpa.domain.Specification;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Feature: Component Tests - Access Control<br/>
|
|
||||||
* Story: Test Target Access Controller
|
|
||||||
*/
|
|
||||||
class TargetAccessControllerTest extends AbstractAccessControllerTest {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
AutoAssignChecker autoAssignChecker;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies read access rules for targets
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void verifyTargetReadOperations() {
|
|
||||||
permitAllOperations(AccessController.Operation.CREATE);
|
|
||||||
|
|
||||||
final Target permittedTarget = targetManagement
|
|
||||||
.create(entityFactory.target().create().controllerId("device01").status(TargetUpdateStatus.REGISTERED));
|
|
||||||
|
|
||||||
final Target hiddenTarget = targetManagement
|
|
||||||
.create(entityFactory.target().create().controllerId("device02").status(TargetUpdateStatus.REGISTERED));
|
|
||||||
|
|
||||||
// define access controlling rule
|
|
||||||
defineAccess(AccessController.Operation.READ, permittedTarget);
|
|
||||||
|
|
||||||
// verify targetManagement#findAll
|
|
||||||
assertThat(targetManagement.findAll(Pageable.unpaged()).get().map(Identifiable::getId).toList())
|
|
||||||
.containsOnly(permittedTarget.getId());
|
|
||||||
|
|
||||||
// verify targetManagement#findByRsql
|
|
||||||
assertThat(targetManagement.findByRsql("id==*", Pageable.unpaged()).get().map(Identifiable::getId).toList())
|
|
||||||
.containsOnly(permittedTarget.getId());
|
|
||||||
|
|
||||||
// verify targetManagement#findByUpdateStatus
|
|
||||||
assertThat(targetManagement.findByUpdateStatus(TargetUpdateStatus.REGISTERED, Pageable.unpaged()).get()
|
|
||||||
.map(Identifiable::getId).toList()).containsOnly(permittedTarget.getId());
|
|
||||||
|
|
||||||
// verify targetManagement#getByControllerID
|
|
||||||
assertThat(targetManagement.getByControllerID(permittedTarget.getControllerId())).isPresent();
|
|
||||||
final String hiddenTargetControllerId = hiddenTarget.getControllerId();
|
|
||||||
assertThatThrownBy(() -> targetManagement.getByControllerID(hiddenTargetControllerId))
|
|
||||||
.as("Missing read permissions for hidden target.")
|
|
||||||
.isInstanceOf(InsufficientPermissionException.class);
|
|
||||||
|
|
||||||
// verify targetManagement#getByControllerID
|
|
||||||
assertThat(targetManagement
|
|
||||||
.getByControllerID(Arrays.asList(permittedTarget.getControllerId(), hiddenTargetControllerId))
|
|
||||||
.stream().map(Identifiable::getId).toList()).containsOnly(permittedTarget.getId());
|
|
||||||
|
|
||||||
// verify targetManagement#get
|
|
||||||
assertThat(targetManagement.get(permittedTarget.getId())).isPresent();
|
|
||||||
assertThat(targetManagement.get(hiddenTarget.getId())).isEmpty();
|
|
||||||
|
|
||||||
// verify targetManagement#get
|
|
||||||
assertThat(targetManagement.get(Arrays.asList(permittedTarget.getId(), hiddenTarget.getId())).stream()
|
|
||||||
.map(Identifiable::getId).toList()).containsOnly(permittedTarget.getId());
|
|
||||||
|
|
||||||
// verify targetManagement#getControllerAttributes
|
|
||||||
assertThat(targetManagement.getControllerAttributes(permittedTarget.getControllerId())).isEmpty();
|
|
||||||
assertThatThrownBy(() -> targetManagement.getControllerAttributes(hiddenTargetControllerId))
|
|
||||||
.as("Target should not be found.")
|
|
||||||
.isInstanceOf(InsufficientPermissionException.class);
|
|
||||||
|
|
||||||
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement
|
|
||||||
.create(entityFactory.targetFilterQuery().create().name("test").query("id==*"));
|
|
||||||
|
|
||||||
// verify targetManagement#findByTargetFilterQuery
|
|
||||||
assertThat(targetManagement.findByTargetFilterQuery(targetFilterQuery.getId(), Pageable.unpaged()).get()
|
|
||||||
.map(Identifiable::getId).toList()).containsOnly(permittedTarget.getId());
|
|
||||||
|
|
||||||
// verify targetManagement#findByTargetFilterQuery (used by UI)
|
|
||||||
assertThat(targetManagement.findByFilters(new FilterParams(null, null, null, null), Pageable.unpaged()).get()
|
|
||||||
.map(Identifiable::getId).toList()).containsOnly(permittedTarget.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
void verifyTagFilteringAndManagement() {
|
|
||||||
// permit all operations first to prepare test setup
|
|
||||||
permitAllOperations(AccessController.Operation.READ);
|
|
||||||
permitAllOperations(AccessController.Operation.CREATE);
|
|
||||||
permitAllOperations(AccessController.Operation.UPDATE);
|
|
||||||
|
|
||||||
final Target permittedTarget = targetManagement
|
|
||||||
.create(entityFactory.target().create().controllerId("device01").status(TargetUpdateStatus.REGISTERED));
|
|
||||||
|
|
||||||
final Target readOnlyTarget = targetManagement
|
|
||||||
.create(entityFactory.target().create().controllerId("device02").status(TargetUpdateStatus.REGISTERED));
|
|
||||||
final String readOnlyTargetControllerId = readOnlyTarget.getControllerId();
|
|
||||||
|
|
||||||
final Target hiddenTarget = targetManagement
|
|
||||||
.create(entityFactory.target().create().controllerId("device03").status(TargetUpdateStatus.REGISTERED));
|
|
||||||
|
|
||||||
final Long myTagId = targetTagManagement.create(entityFactory.tag().create().name("myTag")).getId();
|
|
||||||
|
|
||||||
// perform tag assignment before setting access rules
|
|
||||||
targetManagement.assignTag(Arrays.asList(permittedTarget.getControllerId(), readOnlyTargetControllerId,
|
|
||||||
hiddenTarget.getControllerId()), myTagId);
|
|
||||||
|
|
||||||
// define access controlling rule
|
|
||||||
testAccessControlManger.deleteAllRules();
|
|
||||||
defineAccess(AccessController.Operation.READ, permittedTarget, readOnlyTarget);
|
|
||||||
// allow update operation
|
|
||||||
// allow update operation
|
|
||||||
defineAccess(AccessController.Operation.UPDATE, permittedTarget);
|
|
||||||
|
|
||||||
// verify targetManagement#findByTag
|
|
||||||
assertThat(
|
|
||||||
targetManagement.findByTag(myTagId, Pageable.unpaged()).get().map(Identifiable::getId).toList())
|
|
||||||
.containsOnly(permittedTarget.getId(), readOnlyTarget.getId());
|
|
||||||
|
|
||||||
// verify targetManagement#findByRsqlAndTag
|
|
||||||
assertThat(targetManagement.findByRsqlAndTag("id==*", myTagId, Pageable.unpaged()).get()
|
|
||||||
.map(Identifiable::getId).toList()).containsOnly(permittedTarget.getId(), readOnlyTarget.getId());
|
|
||||||
|
|
||||||
// verify targetManagement#assignTag on permitted target
|
|
||||||
assertThat(targetManagement.assignTag(Collections.singletonList(permittedTarget.getControllerId()), myTagId))
|
|
||||||
.hasSize(1);
|
|
||||||
// verify targetManagement#unassignTag on permitted target
|
|
||||||
assertThat(targetManagement.unassignTag(Collections.singletonList(permittedTarget.getControllerId()), myTagId))
|
|
||||||
.hasSize(1);
|
|
||||||
// verify targetManagement#assignTag on permitted target
|
|
||||||
assertThat(targetManagement.assignTag(Collections.singletonList(permittedTarget.getControllerId()), myTagId))
|
|
||||||
.hasSize(1);
|
|
||||||
// verify targetManagement#unAssignTag on permitted target
|
|
||||||
assertThat(targetManagement.unassignTag(List.of(permittedTarget.getControllerId()), myTagId).get(0).getControllerId())
|
|
||||||
.isEqualTo(permittedTarget.getControllerId());
|
|
||||||
|
|
||||||
// assignment is denied for readOnlyTarget (read, but no update permissions)
|
|
||||||
// No exception has been thrown - because no real change is done
|
|
||||||
// assertThatThrownBy(() -> {
|
|
||||||
// targetManagement
|
|
||||||
// .assignTag(List.of(readOnlyTarget.getControllerId()), myTag.getId())
|
|
||||||
// .getUnassigned();
|
|
||||||
// }).as("Missing update permissions for target to toggle tag assignment.")
|
|
||||||
// .isInstanceOf(InsufficientPermissionException.class);
|
|
||||||
|
|
||||||
// assignment is denied for readOnlyTarget (read, but no update permissions)
|
|
||||||
final List<String> readTargetControllerIdList = Collections.singletonList(readOnlyTargetControllerId);
|
|
||||||
assertThatThrownBy(() -> targetManagement.assignTag(readTargetControllerIdList, myTagId))
|
|
||||||
.as("Missing update permissions for target to toggle tag assignment.")
|
|
||||||
.isInstanceOfAny(InsufficientPermissionException.class);
|
|
||||||
|
|
||||||
// assignment is denied for readOnlyTarget (read, but no update permissions)
|
|
||||||
final List<String> readOnlyTargetControllerIdList = List.of(readOnlyTargetControllerId);
|
|
||||||
assertThatThrownBy(() -> targetManagement.unassignTag(readOnlyTargetControllerIdList, myTagId))
|
|
||||||
.as("Missing update permissions for target to toggle tag assignment.")
|
|
||||||
.isInstanceOf(InsufficientPermissionException.class);
|
|
||||||
|
|
||||||
// assignment is denied for hiddenTarget since it's hidden
|
|
||||||
final List<String> hiddenTargetControllerIdList = Collections.singletonList(hiddenTarget.getControllerId());
|
|
||||||
assertThatThrownBy(() -> targetManagement.assignTag(hiddenTargetControllerIdList, myTagId))
|
|
||||||
.as("Missing update permissions for target to toggle tag assignment.")
|
|
||||||
.isInstanceOf(InsufficientPermissionException.class);
|
|
||||||
|
|
||||||
// assignment is denied for hiddenTarget since it's hidden
|
|
||||||
assertThatThrownBy(() -> targetManagement.assignTag(hiddenTargetControllerIdList, myTagId))
|
|
||||||
.as("Missing update permissions for target to toggle tag assignment.")
|
|
||||||
.isInstanceOf(InsufficientPermissionException.class);
|
|
||||||
|
|
||||||
// assignment is denied for hiddenTarget since it's hidden
|
|
||||||
assertThatThrownBy(() -> targetManagement.unassignTag(hiddenTargetControllerIdList, myTagId))
|
|
||||||
.as("Missing update permissions for target to toggle tag assignment.")
|
|
||||||
.isInstanceOf(InsufficientPermissionException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies rules for target assignment
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void verifyTargetAssignment() {
|
|
||||||
permitAllOperations(AccessController.Operation.READ);
|
|
||||||
permitAllOperations(AccessController.Operation.CREATE);
|
|
||||||
permitAllOperations(AccessController.Operation.UPDATE);
|
|
||||||
|
|
||||||
final Long dsId = testdataFactory.createDistributionSet("myDs").getId();
|
|
||||||
distributionSetManagement.lock(dsId);
|
|
||||||
// entities created - reset rules
|
|
||||||
testAccessControlManger.deleteAllRules();
|
|
||||||
|
|
||||||
permitAllOperations(AccessController.Operation.READ);
|
|
||||||
permitAllOperations(AccessController.Operation.CREATE);
|
|
||||||
|
|
||||||
final Target permittedTarget = targetManagement
|
|
||||||
.create(entityFactory.target().create().controllerId("device01").status(TargetUpdateStatus.REGISTERED));
|
|
||||||
final String hiddenTargetControllerId = targetManagement
|
|
||||||
.create(entityFactory.target().create().controllerId("device02").status(TargetUpdateStatus.REGISTERED))
|
|
||||||
.getControllerId();
|
|
||||||
|
|
||||||
// define access controlling rule
|
|
||||||
overwriteAccess(AccessController.Operation.READ, permittedTarget);
|
|
||||||
|
|
||||||
// verify targetManagement#findByUpdateStatus before assignment
|
|
||||||
assertThat(targetManagement.findByUpdateStatus(TargetUpdateStatus.REGISTERED, Pageable.unpaged()).get()
|
|
||||||
.map(Identifiable::getId).toList()).containsOnly(permittedTarget.getId());
|
|
||||||
|
|
||||||
testAccessControlManger.defineAccessRule(
|
|
||||||
JpaTarget.class, AccessController.Operation.UPDATE,
|
|
||||||
TargetSpecifications.hasId(permittedTarget.getId()),
|
|
||||||
target -> target.getId().equals(permittedTarget.getId()));
|
|
||||||
|
|
||||||
assertThat(assignDistributionSet(dsId, permittedTarget.getControllerId()).getAssigned()).isEqualTo(1);
|
|
||||||
// assigning of non allowed target behaves as not found
|
|
||||||
assertThatThrownBy(() -> assignDistributionSet(dsId, hiddenTargetControllerId)).isInstanceOf(AssertionError.class);
|
|
||||||
|
|
||||||
// verify targetManagement#findByUpdateStatus(REGISTERED) after assignment
|
|
||||||
assertThat(targetManagement.findByUpdateStatus(TargetUpdateStatus.REGISTERED, Pageable.unpaged())
|
|
||||||
.getTotalElements()).isZero();
|
|
||||||
|
|
||||||
// verify targetManagement#findByUpdateStatus(PENDING) after assignment
|
|
||||||
assertThat(targetManagement.findByUpdateStatus(TargetUpdateStatus.PENDING, Pageable.unpaged()).get()
|
|
||||||
.map(Identifiable::getId).toList()).containsOnly(permittedTarget.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies rules for target assignment
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void verifyTargetAssignmentOnNonUpdatableTarget() {
|
|
||||||
permitAllOperations(AccessController.Operation.READ);
|
|
||||||
permitAllOperations(AccessController.Operation.CREATE);
|
|
||||||
permitAllOperations(AccessController.Operation.UPDATE);
|
|
||||||
|
|
||||||
final Long firstDsId = testdataFactory.createDistributionSet("myDs").getId();
|
|
||||||
distributionSetManagement.lock(firstDsId);
|
|
||||||
final DistributionSet secondDs = testdataFactory.createDistributionSet("anotherDs");
|
|
||||||
distributionSetManagement.lock(secondDs.getId());
|
|
||||||
// entities created - reset rules
|
|
||||||
testAccessControlManger.deleteAllRules();
|
|
||||||
|
|
||||||
permitAllOperations(AccessController.Operation.READ);
|
|
||||||
permitAllOperations(AccessController.Operation.CREATE);
|
|
||||||
|
|
||||||
final Target manageableTarget = targetManagement
|
|
||||||
.create(entityFactory.target().create().controllerId("device01").status(TargetUpdateStatus.REGISTERED));
|
|
||||||
final Target readOnlyTarget = targetManagement
|
|
||||||
.create(entityFactory.target().create().controllerId("device02").status(TargetUpdateStatus.REGISTERED));
|
|
||||||
|
|
||||||
// overwriting full access controlling rule
|
|
||||||
overwriteAccess(AccessController.Operation.READ, manageableTarget, readOnlyTarget);
|
|
||||||
overwriteAccess(AccessController.Operation.UPDATE, manageableTarget);
|
|
||||||
|
|
||||||
// assignment is permitted for manageableTarget
|
|
||||||
assertThat(assignDistributionSet(firstDsId, manageableTarget.getControllerId()).getAssigned()).isEqualTo(1);
|
|
||||||
|
|
||||||
// assignment is denied for readOnlyTarget (read, but no update permissions)
|
|
||||||
final var readOnlyTargetControllerId = readOnlyTarget.getControllerId();
|
|
||||||
assertThatThrownBy(() -> assignDistributionSet(firstDsId, readOnlyTargetControllerId)).isInstanceOf(AssertionError.class);
|
|
||||||
|
|
||||||
// bunch assignment skips denied denied since at least one target without update
|
|
||||||
// permissions is present
|
|
||||||
assertThat(assignDistributionSet(secondDs.getId(),
|
|
||||||
Arrays.asList(readOnlyTargetControllerId, manageableTarget.getControllerId()),
|
|
||||||
Action.ActionType.FORCED).getAssigned()).isEqualTo(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies only manageable targets are part of the rollout
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void verifyRolloutTargetScope() {
|
|
||||||
permitAllOperations(AccessController.Operation.READ);
|
|
||||||
permitAllOperations(AccessController.Operation.CREATE);
|
|
||||||
permitAllOperations(AccessController.Operation.UPDATE);
|
|
||||||
final DistributionSet ds = testdataFactory.createDistributionSet("myDs");
|
|
||||||
distributionSetManagement.lock(ds.getId());
|
|
||||||
// entities created - reset rules
|
|
||||||
testAccessControlManger.deleteAllRules();
|
|
||||||
|
|
||||||
permitAllOperations(AccessController.Operation.READ);
|
|
||||||
permitAllOperations(AccessController.Operation.CREATE);
|
|
||||||
|
|
||||||
final List<Target> updateTargets = testdataFactory.createTargets("update1", "update2", "update3");
|
|
||||||
final List<Target> readTargets = testdataFactory.createTargets("read1", "read2", "read3", "read4");
|
|
||||||
final List<Target> hiddenTargets = testdataFactory.createTargets("hidden1", "hidden2", "hidden3", "hidden4", "hidden5");
|
|
||||||
|
|
||||||
defineAccess(AccessController.Operation.UPDATE, updateTargets);
|
|
||||||
overwriteAccess(AccessController.Operation.READ, merge(readTargets, updateTargets));
|
|
||||||
|
|
||||||
final Rollout rollout = testdataFactory.createRolloutByVariables(
|
|
||||||
"testRollout", "description", updateTargets.size(), "id==*", ds, "50", "5");
|
|
||||||
|
|
||||||
assertThat(rollout.getTotalTargets()).isEqualTo(updateTargets.size());
|
|
||||||
|
|
||||||
final List<RolloutGroup> content = rolloutGroupManagement.findByRollout(rollout.getId(), Pageable.unpaged()).getContent();
|
|
||||||
assertThat(content).hasSize(updateTargets.size());
|
|
||||||
|
|
||||||
final List<Target> rolloutTargets = content.stream().flatMap(
|
|
||||||
group -> rolloutGroupManagement.findTargetsOfRolloutGroup(group.getId(), Pageable.unpaged()).get())
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
assertThat(rolloutTargets).hasSize(updateTargets.size()).allMatch(
|
|
||||||
target -> updateTargets.stream().anyMatch(readTarget -> readTarget.getId().equals(target.getId())))
|
|
||||||
.noneMatch(target -> readTargets.stream()
|
|
||||||
.anyMatch(readTarget -> readTarget.getId().equals(target.getId())))
|
|
||||||
.noneMatch(target -> hiddenTargets.stream()
|
|
||||||
.anyMatch(readTarget -> readTarget.getId().equals(target.getId())));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies only manageable targets are part of an auto assignment.
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void verifyAutoAssignmentTargetScope() {
|
|
||||||
permitAllOperations(AccessController.Operation.READ);
|
|
||||||
permitAllOperations(AccessController.Operation.CREATE);
|
|
||||||
permitAllOperations(AccessController.Operation.UPDATE);
|
|
||||||
final DistributionSet distributionSet = testdataFactory.createDistributionSet();
|
|
||||||
distributionSetManagement.lock(distributionSet.getId());
|
|
||||||
// entities created - reset rules
|
|
||||||
testAccessControlManger.deleteAllRules();
|
|
||||||
|
|
||||||
permitAllOperations(AccessController.Operation.CREATE);
|
|
||||||
|
|
||||||
final List<Target> updateTargets = testdataFactory.createTargets("update1", "update2", "update3");
|
|
||||||
final List<Target> readTargets = testdataFactory.createTargets("read1", "read2", "read3", "read4");
|
|
||||||
final List<Target> hiddenTargets = testdataFactory.createTargets("hidden1", "hidden2", "hidden3", "hidden4",
|
|
||||||
"hidden5");
|
|
||||||
|
|
||||||
defineAccess(AccessController.Operation.UPDATE, updateTargets);
|
|
||||||
defineAccess(AccessController.Operation.READ, merge(updateTargets, readTargets));
|
|
||||||
|
|
||||||
final TargetFilterQuery targetFilterQuery = targetFilterQueryManagement
|
|
||||||
.create(entityFactory.targetFilterQuery().create().name("testName").query("id==*"));
|
|
||||||
|
|
||||||
testAccessControlManger.defineAccessRule(
|
|
||||||
JpaDistributionSet.class, AccessController.Operation.READ,
|
|
||||||
dsById(distributionSet.getId()),
|
|
||||||
ds -> ds.getId().equals(distributionSet.getId()));
|
|
||||||
|
|
||||||
targetFilterQueryManagement.updateAutoAssignDS(entityFactory.targetFilterQuery()
|
|
||||||
.updateAutoAssign(targetFilterQuery.getId()).ds(distributionSet.getId()));
|
|
||||||
|
|
||||||
autoAssignChecker.checkAllTargets();
|
|
||||||
|
|
||||||
assertThat(targetManagement.findByAssignedDistributionSet(distributionSet.getId(), Pageable.unpaged())
|
|
||||||
.getContent())
|
|
||||||
.hasSize(updateTargets.size())
|
|
||||||
.allMatch(assignedTarget -> updateTargets.stream()
|
|
||||||
.anyMatch(updateTarget -> updateTarget.getId().equals(assignedTarget.getId())))
|
|
||||||
.noneMatch(assignedTarget -> readTargets.stream()
|
|
||||||
.anyMatch(updateTarget -> updateTarget.getId().equals(assignedTarget.getId())))
|
|
||||||
.noneMatch(assignedTarget -> hiddenTargets.stream()
|
|
||||||
.anyMatch(updateTarget -> updateTarget.getId().equals(assignedTarget.getId())));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void defineAccess(final AccessController.Operation operation, final Target... target) {
|
|
||||||
defineAccess(operation, List.of(target));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void defineAccess(final AccessController.Operation operation, final List<Target> targets) {
|
|
||||||
final List<Long> ids = targets.stream().map(Target::getId).toList();
|
|
||||||
testAccessControlManger.defineAccessRule(
|
|
||||||
JpaTarget.class, operation,
|
|
||||||
TargetSpecifications.hasIdIn(ids),
|
|
||||||
target -> ids.contains(target.getId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void overwriteAccess(final AccessController.Operation operation, final Target... target) {
|
|
||||||
overwriteAccess(operation, List.of(target));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void overwriteAccess(final AccessController.Operation operation, final List<Target> targets) {
|
|
||||||
final List<Long> ids = targets.stream().map(Target::getId).toList();
|
|
||||||
testAccessControlManger.overwriteAccessRule(
|
|
||||||
JpaTarget.class, operation,
|
|
||||||
TargetSpecifications.hasIdIn(ids),
|
|
||||||
target -> ids.contains(target.getId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Specification<JpaDistributionSet> dsById(final Long distid) {
|
|
||||||
return (dsRoot, query, cb) -> {
|
|
||||||
final Predicate predicate = cb.equal(dsRoot.get(JpaDistributionSet_.id), distid);
|
|
||||||
query.distinct(true);
|
|
||||||
return predicate;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2023 Bosch.IO GmbH and others
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made
|
|
||||||
* available under the terms of the Eclipse Public License 2.0
|
|
||||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.eclipse.hawkbit.repository.jpa.acm.controller;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import org.eclipse.hawkbit.repository.Identifiable;
|
|
||||||
import org.eclipse.hawkbit.repository.builder.TargetTypeCreate;
|
|
||||||
import org.eclipse.hawkbit.repository.builder.TargetTypeUpdate;
|
|
||||||
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
|
|
||||||
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.specifications.TargetTypeSpecification;
|
|
||||||
import org.eclipse.hawkbit.repository.model.TargetType;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
import org.springframework.data.domain.Pageable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Feature: Component Tests - Access Control<br/>
|
|
||||||
* Story: Test Target Type Access Controller
|
|
||||||
*/
|
|
||||||
class TargetTypeAccessControllerTest extends AbstractAccessControllerTest {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies read access rules for target types
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void verifyTargetTypeReadOperations() {
|
|
||||||
permitAllOperations(AccessController.Operation.CREATE);
|
|
||||||
final TargetType permittedTargetType = targetTypeManagement.create(entityFactory.targetType().create().name("type1"));
|
|
||||||
final TargetType hiddenTargetType = targetTypeManagement.create(entityFactory.targetType().create().name("type2"));
|
|
||||||
|
|
||||||
// define access controlling rule
|
|
||||||
defineAccess(AccessController.Operation.READ, permittedTargetType);
|
|
||||||
|
|
||||||
// verify targetTypeManagement#findAll
|
|
||||||
assertThat(targetTypeManagement.findAll(Pageable.unpaged()).get().map(Identifiable::getId).toList())
|
|
||||||
.containsOnly(permittedTargetType.getId());
|
|
||||||
|
|
||||||
// verify targetTypeManagement#findByRsql
|
|
||||||
assertThat(targetTypeManagement.findByRsql("name==*", Pageable.unpaged()).get().map(Identifiable::getId).toList())
|
|
||||||
.containsOnly(permittedTargetType.getId());
|
|
||||||
|
|
||||||
// verify targetTypeManagement#findByName
|
|
||||||
assertThat(targetTypeManagement.findByName(permittedTargetType.getName(), Pageable.unpaged()).getContent())
|
|
||||||
.hasSize(1).satisfies(results ->
|
|
||||||
assertThat(results.get(0).getId()).isEqualTo(permittedTargetType.getId()));
|
|
||||||
assertThat(targetTypeManagement.findByName(hiddenTargetType.getName(), Pageable.unpaged())).isEmpty();
|
|
||||||
|
|
||||||
// verify targetTypeManagement#count
|
|
||||||
assertThat(targetTypeManagement.count()).isEqualTo(1);
|
|
||||||
|
|
||||||
// verify targetTypeManagement#countByName
|
|
||||||
assertThat(targetTypeManagement.countByName(permittedTargetType.getName())).isEqualTo(1);
|
|
||||||
assertThat(targetTypeManagement.countByName(hiddenTargetType.getName())).isZero();
|
|
||||||
|
|
||||||
// verify targetTypeManagement#countByName
|
|
||||||
assertThat(targetTypeManagement.countByName(permittedTargetType.getName())).isEqualTo(1);
|
|
||||||
assertThat(targetTypeManagement.countByName(hiddenTargetType.getName())).isZero();
|
|
||||||
|
|
||||||
// verify targetTypeManagement#get by id
|
|
||||||
assertThat(targetTypeManagement.get(permittedTargetType.getId())).isPresent();
|
|
||||||
final Long hiddenTargetTypeId = hiddenTargetType.getId();
|
|
||||||
assertThat(targetTypeManagement.get(hiddenTargetTypeId)).isEmpty();
|
|
||||||
|
|
||||||
// verify targetTypeManagement#getByName
|
|
||||||
assertThat(targetTypeManagement.getByName(permittedTargetType.getName())).isPresent();
|
|
||||||
assertThat(targetTypeManagement.getByName(hiddenTargetType.getName())).isEmpty();
|
|
||||||
|
|
||||||
// verify targetTypeManagement#get by ids
|
|
||||||
assertThat(targetTypeManagement.get(Arrays.asList(permittedTargetType.getId(), hiddenTargetTypeId))
|
|
||||||
.stream().map(Identifiable::getId).toList()).containsOnly(permittedTargetType.getId());
|
|
||||||
|
|
||||||
// verify targetTypeManagement#update is not possible. Assert exception thrown.
|
|
||||||
final TargetTypeUpdate targetTypeUpdate = entityFactory.targetType().update(hiddenTargetTypeId)
|
|
||||||
.name(hiddenTargetType.getName() + "/new").description("newDesc");
|
|
||||||
assertThatThrownBy(() -> targetTypeManagement.update(targetTypeUpdate))
|
|
||||||
.as("Target type update shouldn't be allowed since the target type is not visible.")
|
|
||||||
.isInstanceOf(EntityNotFoundException.class);
|
|
||||||
|
|
||||||
// verify targetTypeManagement#delete is not possible. Assert exception thrown.
|
|
||||||
assertThatThrownBy(() -> targetTypeManagement.delete(hiddenTargetTypeId))
|
|
||||||
.as("Target type delete shouldn't be allowed since the target type is not visible.")
|
|
||||||
.isInstanceOf(EntityNotFoundException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies delete access rules for target types
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void verifyTargetTypeDeleteOperations() {
|
|
||||||
permitAllOperations(AccessController.Operation.CREATE);
|
|
||||||
final TargetType manageableTargetType = targetTypeManagement.create(entityFactory.targetType().create().name("type1"));
|
|
||||||
|
|
||||||
final TargetType readOnlyTargetType = targetTypeManagement.create(entityFactory.targetType().create().name("type2"));
|
|
||||||
|
|
||||||
// define access controlling rule to allow reading both types
|
|
||||||
defineAccess(AccessController.Operation.READ, manageableTargetType, readOnlyTargetType);
|
|
||||||
|
|
||||||
// permit operation to delete permittedTargetType
|
|
||||||
defineAccess(AccessController.Operation.DELETE, manageableTargetType);
|
|
||||||
|
|
||||||
// delete the manageableTargetType
|
|
||||||
targetTypeManagement.delete(manageableTargetType.getId());
|
|
||||||
|
|
||||||
// verify targetTypeManagement#delete for readOnlyTargetType is not possible
|
|
||||||
final Long readOnlyTargetTypeId = readOnlyTargetType.getId();
|
|
||||||
assertThatThrownBy(() -> targetTypeManagement.delete(readOnlyTargetTypeId))
|
|
||||||
.isInstanceOfAny(InsufficientPermissionException.class, EntityNotFoundException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies update operation for target types
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void verifyTargetTypeUpdateOperations() {
|
|
||||||
permitAllOperations(AccessController.Operation.CREATE);
|
|
||||||
final TargetType manageableTargetType = targetTypeManagement
|
|
||||||
.create(entityFactory.targetType().create().name("type1"));
|
|
||||||
|
|
||||||
final TargetType readOnlyTargetType = targetTypeManagement
|
|
||||||
.create(entityFactory.targetType().create().name("type2"));
|
|
||||||
|
|
||||||
// define access controlling rule to allow reading both types
|
|
||||||
defineAccess(AccessController.Operation.READ, manageableTargetType, readOnlyTargetType);
|
|
||||||
|
|
||||||
// permit updating the manageableTargetType
|
|
||||||
defineAccess(AccessController.Operation.UPDATE, manageableTargetType);
|
|
||||||
|
|
||||||
// update the manageableTargetType
|
|
||||||
targetTypeManagement.update(entityFactory.targetType().update(manageableTargetType.getId())
|
|
||||||
.name(manageableTargetType.getName() + "/new").description("newDesc"));
|
|
||||||
|
|
||||||
// verify targetTypeManagement#update for readOnlyTargetType is not possible
|
|
||||||
final TargetTypeUpdate targetTypeUpdate = entityFactory.targetType().update(readOnlyTargetType.getId())
|
|
||||||
.name(readOnlyTargetType.getName() + "/new").description("newDesc");
|
|
||||||
assertThatThrownBy(() -> targetTypeManagement.update(targetTypeUpdate))
|
|
||||||
.isInstanceOf(InsufficientPermissionException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies create operation blocked by controller
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
void verifyTargetTypeCreationBlockedByAccessController() {
|
|
||||||
defineAccess(AccessController.Operation.CREATE); // allows for none
|
|
||||||
// verify targetTypeManagement#create for any type
|
|
||||||
final TargetTypeCreate targetTypeCreate = entityFactory.targetType().create().name("type1");
|
|
||||||
assertThatThrownBy(() -> targetTypeManagement.create(targetTypeCreate))
|
|
||||||
.as("Target type create shouldn't be allowed since the target type is not visible.")
|
|
||||||
.isInstanceOf(InsufficientPermissionException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void defineAccess(final AccessController.Operation operation, final TargetType... targetTypes) {
|
|
||||||
final List<Long> ids = Stream.of(targetTypes).map(TargetType::getId).toList();
|
|
||||||
testAccessControlManger.defineAccessRule(
|
|
||||||
JpaTargetType.class, operation,
|
|
||||||
TargetTypeSpecification.hasIdIn(ids),
|
|
||||||
targetType -> ids.contains(targetType.getId()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2023 Bosch.IO GmbH and others
|
|
||||||
*
|
|
||||||
* This program and the accompanying materials are made
|
|
||||||
* available under the terms of the Eclipse Public License 2.0
|
|
||||||
* which is available at https://www.eclipse.org/legal/epl-2.0/
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: EPL-2.0
|
|
||||||
*/
|
|
||||||
package org.eclipse.hawkbit.repository.jpa.acm.controller;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_;
|
|
||||||
import org.springframework.data.jpa.domain.Specification;
|
|
||||||
|
|
||||||
public class TestAccessControlManger {
|
|
||||||
|
|
||||||
private final Map<AccessRuleId<?>, AccessRule<?>> accessRules = new HashMap<>();
|
|
||||||
|
|
||||||
public void deleteAllRules() {
|
|
||||||
accessRules.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> void defineAccessRule(
|
|
||||||
final Class<T> ruleClass, final AccessController.Operation operation,
|
|
||||||
final Specification<T> specification, final Predicate<T> check) {
|
|
||||||
defineAccessRule(ruleClass, operation, specification, check, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> void overwriteAccessRule(
|
|
||||||
final Class<T> ruleClass, final AccessController.Operation operation,
|
|
||||||
final Specification<T> specification, final Predicate<T> check) {
|
|
||||||
defineAccessRule(ruleClass, operation, specification, check, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T> void defineAccessRule(
|
|
||||||
final Class<T> ruleClass, final AccessController.Operation operation,
|
|
||||||
final Specification<T> specification, final Predicate<T> check, final boolean overwrite) {
|
|
||||||
final AccessRuleId<T> ruleId = new AccessRuleId<>(ruleClass, operation);
|
|
||||||
if (!overwrite && accessRules.containsKey(ruleId)) {
|
|
||||||
throw new IllegalStateException("Access rule already defined for " + ruleId + "! You should explicitly set overwrite to true.");
|
|
||||||
}
|
|
||||||
accessRules.put(ruleId, new AccessRule<>(specification, check));
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T extends AbstractJpaBaseEntity> Specification<T> getAccessRule(final Class<T> ruleClass,
|
|
||||||
final AccessController.Operation operation) {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
final AccessRule<T> accessRule = (AccessRule<T>) accessRules.getOrDefault(new AccessRuleId<>(ruleClass, operation), null);
|
|
||||||
if (accessRule == null) {
|
|
||||||
return nop();
|
|
||||||
} else {
|
|
||||||
return accessRule.specification();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> void assertOperation(final Class<T> ruleClass, final AccessController.Operation operation, final List<T> entities) {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
final AccessRule<T> accessRule = (AccessRule<T>) accessRules.getOrDefault(new AccessRuleId<>(ruleClass, operation), null);
|
|
||||||
if (accessRule == null) {
|
|
||||||
throw new InsufficientPermissionException("No access define - reject all");
|
|
||||||
} else {
|
|
||||||
for (final T entity : entities) {
|
|
||||||
if (!accessRule.checker.test(entity)) {
|
|
||||||
throw new InsufficientPermissionException("Access to " + ruleClass.getName() + "/" + entity + " not allowed by checker!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <T extends AbstractJpaBaseEntity> Specification<T> nop() {
|
|
||||||
return (targetRoot, query, cb) -> cb.equal(targetRoot.get(AbstractJpaBaseEntity_.id), -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private record AccessRuleId<T>(Class<T> ruleClass, AccessController.Operation operation) {}
|
|
||||||
|
|
||||||
private record AccessRule<T>(Specification<T> specification, Predicate<T> checker) {}
|
|
||||||
}
|
|
||||||
@@ -253,8 +253,6 @@ class ArtifactManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Test method for {@link org.eclipse.hawkbit.repository.ArtifactManagement#delete(long)}.
|
* Test method for {@link org.eclipse.hawkbit.repository.ArtifactManagement#delete(long)}.
|
||||||
*/
|
|
||||||
/**
|
|
||||||
* Tests the deletion of a local artifact including metadata.
|
* Tests the deletion of a local artifact including metadata.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@@ -585,7 +583,7 @@ class ArtifactManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private <T> T runAsTenant(final String tenant, final Callable<T> callable) throws Exception {
|
private <T> T runAsTenant(final String tenant, final Callable<T> callable) throws Exception {
|
||||||
return SecurityContextSwitch.runAs(SecurityContextSwitch.withUserAndTenantAllSpPermissions("user", tenant), callable);
|
return SecurityContextSwitch.callAs(SecurityContextSwitch.withUserAndTenantAllSpPermissions("user", tenant), callable);
|
||||||
}
|
}
|
||||||
|
|
||||||
private SoftwareModule createSoftwareModuleForTenant(final String tenant) throws Exception {
|
private SoftwareModule createSoftwareModuleForTenant(final String tenant) throws Exception {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpre
|
|||||||
import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS;
|
import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS;
|
||||||
import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_MAX;
|
import static org.eclipse.hawkbit.repository.jpa.configuration.Constants.TX_RT_MAX;
|
||||||
import static org.eclipse.hawkbit.repository.model.Action.ActionType.DOWNLOAD_ONLY;
|
import static org.eclipse.hawkbit.repository.model.Action.ActionType.DOWNLOAD_ONLY;
|
||||||
|
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.runAs;
|
||||||
import static org.eclipse.hawkbit.repository.test.util.TestdataFactory.DEFAULT_CONTROLLER_ID;
|
import static org.eclipse.hawkbit.repository.test.util.TestdataFactory.DEFAULT_CONTROLLER_ID;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
@@ -107,11 +108,9 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
testdataFactory.createTarget(controllerId);
|
testdataFactory.createTarget(controllerId);
|
||||||
|
|
||||||
final WithUser withController = SecurityContextSwitch.withController("controller", CONTROLLER_ROLE_ANONYMOUS);
|
final WithUser withController = SecurityContextSwitch.withController("controller", CONTROLLER_ROLE_ANONYMOUS);
|
||||||
assertThatExceptionOfType(AssignmentQuotaExceededException.class).isThrownBy(() -> SecurityContextSwitch
|
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
|
||||||
.runAs(withController, () -> {
|
.isThrownBy(() -> runAs(withController, () -> writeAttributes(controllerId, allowedAttributes + 1, "key", "value")))
|
||||||
writeAttributes(controllerId, allowedAttributes + 1, "key", "value");
|
.withMessageContaining("" + allowedAttributes);
|
||||||
return null;
|
|
||||||
})).withMessageContaining("" + allowedAttributes);
|
|
||||||
|
|
||||||
// verify that no attributes have been written
|
// verify that no attributes have been written
|
||||||
assertThat(targetManagement.getControllerAttributes(controllerId)).isEmpty();
|
assertThat(targetManagement.getControllerAttributes(controllerId)).isEmpty();
|
||||||
@@ -121,13 +120,12 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
SecurityContextSwitch.runAs(withController, () -> {
|
SecurityContextSwitch.runAs(withController, () -> {
|
||||||
writeAttributes(controllerId, allowedAttributes, "key", "value1");
|
writeAttributes(controllerId, allowedAttributes, "key", "value1");
|
||||||
writeAttributes(controllerId, allowedAttributes, "key", "value2");
|
writeAttributes(controllerId, allowedAttributes, "key", "value2");
|
||||||
return null;
|
|
||||||
});
|
});
|
||||||
assertThat(targetManagement.getControllerAttributes(controllerId)).hasSize(10);
|
assertThat(targetManagement.getControllerAttributes(controllerId)).hasSize(10);
|
||||||
|
|
||||||
// Now rite one more
|
// Now rite one more
|
||||||
assertThatExceptionOfType(AssignmentQuotaExceededException.class).isThrownBy(() -> SecurityContextSwitch
|
assertThatExceptionOfType(AssignmentQuotaExceededException.class).isThrownBy(() -> SecurityContextSwitch
|
||||||
.runAs(withController, () -> {
|
.getAs(withController, () -> {
|
||||||
writeAttributes(controllerId, 1, "additional", "value1");
|
writeAttributes(controllerId, 1, "additional", "value1");
|
||||||
return null;
|
return null;
|
||||||
})).withMessageContaining("" + allowedAttributes);
|
})).withMessageContaining("" + allowedAttributes);
|
||||||
@@ -185,7 +183,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
final Long actionId = createTargetAndAssignDs();
|
final Long actionId = createTargetAndAssignDs();
|
||||||
|
|
||||||
SecurityContextSwitch
|
SecurityContextSwitch
|
||||||
.runAs(SecurityContextSwitch.withController("controller", CONTROLLER_ROLE_ANONYMOUS), () -> {
|
.getAs(SecurityContextSwitch.withController("controller", CONTROLLER_ROLE_ANONYMOUS), () -> {
|
||||||
// Fails as one entry is already in there from the assignment
|
// Fails as one entry is already in there from the assignment
|
||||||
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
|
assertThatExceptionOfType(AssignmentQuotaExceededException.class)
|
||||||
.isThrownBy(() -> writeStatus(actionId, allowStatusEntries))
|
.isThrownBy(() -> writeStatus(actionId, allowStatusEntries))
|
||||||
@@ -345,8 +343,8 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies that management queries react as specified on calls for non existing entities
|
* Verifies that management queries react as specified on calls for non existing entities
|
||||||
* by means of throwing EntityNotFoundException.
|
* by means of throwing EntityNotFoundException.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@ExpectEvents({
|
@ExpectEvents({
|
||||||
@@ -571,7 +569,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller rejects action cancellation with CANCEL_REJECTED status. Action goes back to RUNNING status as it expects
|
* Controller rejects action cancellation with CANCEL_REJECTED status. Action goes back to RUNNING status as it expects
|
||||||
* that the controller will continue the original update.
|
* that the controller will continue the original update.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@@ -639,8 +637,8 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies that assignment verification works based on SHA1 hash. By design it is not important which artifact
|
* Verifies that assignment verification works based on SHA1 hash. By design it is not important which artifact
|
||||||
* is actually used for the check as long as they have an identical binary, i.e. same SHA1 hash.
|
* is actually used for the check as long as they have an identical binary, i.e. same SHA1 hash.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@ExpectEvents({
|
@ExpectEvents({
|
||||||
@@ -878,7 +876,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a controller which does not exist, when a ConcurrencyFailureException is raised, the
|
* Register a controller which does not exist, when a ConcurrencyFailureException is raised, the
|
||||||
* exception is not rethrown when the max retries are not yet reached
|
* exception is not rethrown when the max retries are not yet reached
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@@ -957,7 +955,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retry is aborted when an unchecked exception is thrown and the exception should also be
|
* Retry is aborted when an unchecked exception is thrown and the exception should also be
|
||||||
* rethrown
|
* rethrown
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@@ -1004,7 +1002,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
@Test
|
@Test
|
||||||
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1) })
|
@ExpectEvents({ @Expect(type = TargetCreatedEvent.class, count = 1) })
|
||||||
@SuppressWarnings("java:S2699")
|
@SuppressWarnings("java:S2699")
|
||||||
// java:S2699 - test tests the fired events, no need for assert
|
// java:S2699 - test tests the fired events, no need for assert
|
||||||
void targetPollEventNotSendIfDisabled() {
|
void targetPollEventNotSendIfDisabled() {
|
||||||
repositoryProperties.setPublishTargetPollEvent(false);
|
repositoryProperties.setPublishTargetPollEvent(false);
|
||||||
controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST);
|
controllerManagement.findOrRegisterTargetIfItDoesNotExist("AA", LOCALHOST);
|
||||||
@@ -1121,7 +1119,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Controller tries to send an update feedback after it has been finished which is accepted as the repository is
|
* Controller tries to send an update feedback after it has been finished which is accepted as the repository is
|
||||||
* configured to accept them.
|
* configured to accept them.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@@ -1160,7 +1158,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
final String controllerId = "test123";
|
final String controllerId = "test123";
|
||||||
final Target target = testdataFactory.createTarget(controllerId);
|
final Target target = testdataFactory.createTarget(controllerId);
|
||||||
|
|
||||||
SecurityContextSwitch.runAs(SecurityContextSwitch.withController(
|
SecurityContextSwitch.getAs(SecurityContextSwitch.withController(
|
||||||
"controller",
|
"controller",
|
||||||
CONTROLLER_ROLE_ANONYMOUS, SpPermission.READ_TARGET), () -> {
|
CONTROLLER_ROLE_ANONYMOUS, SpPermission.READ_TARGET), () -> {
|
||||||
addAttributeAndVerify(controllerId);
|
addAttributeAndVerify(controllerId);
|
||||||
@@ -1289,7 +1287,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies that quota is asserted when a controller reports too many DOWNLOADED events for a
|
* Verifies that quota is asserted when a controller reports too many DOWNLOADED events for a
|
||||||
* DOWNLOAD_ONLY action.
|
* DOWNLOAD_ONLY action.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@@ -1516,7 +1514,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies that a target can report FINISHED/ERROR updates for DOWNLOAD_ONLY assignments regardless of
|
* Verifies that a target can report FINISHED/ERROR updates for DOWNLOAD_ONLY assignments regardless of
|
||||||
* repositoryProperties.rejectActionStatusForClosedAction value.
|
* repositoryProperties.rejectActionStatusForClosedAction value.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@@ -1565,7 +1563,7 @@ class ControllerManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifies that a controller can report a FINISHED event for a DOWNLOAD_ONLY action after having
|
* Verifies that a controller can report a FINISHED event for a DOWNLOAD_ONLY action after having
|
||||||
* installed an intermediate update.
|
* installed an intermediate update.
|
||||||
*/
|
*/
|
||||||
@Test
|
@Test
|
||||||
@ExpectEvents({
|
@ExpectEvents({
|
||||||
|
|||||||
@@ -1442,22 +1442,22 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
// create scheduled rollout fails without handle rollout permission
|
// create scheduled rollout fails without handle rollout permission
|
||||||
assertThatExceptionOfType(InsufficientPermissionException.class)
|
assertThatExceptionOfType(InsufficientPermissionException.class)
|
||||||
.as("Insufficient permission exception when startAt and no handle rollout permission")
|
.as("Insufficient permission exception when startAt and no handle rollout permission")
|
||||||
.isThrownBy(() -> SecurityContextSwitch.runAs(
|
.isThrownBy(() -> SecurityContextSwitch.getAs(
|
||||||
userWithoutHandleRollout, () -> createRolloutWithStartAt(rolloutName, filter, distributionSet, 1L)));
|
userWithoutHandleRollout, () -> createRolloutWithStartAt(rolloutName, filter, distributionSet, 1L)));
|
||||||
// same action succeeds with handle rollout permission
|
// same action succeeds with handle rollout permission
|
||||||
SecurityContextSwitch.runAs(
|
SecurityContextSwitch.getAs(
|
||||||
userWithHandleRollout,
|
userWithHandleRollout,
|
||||||
() -> createRolloutWithStartAt(rolloutName + "_withStartTime", filter, distributionSet, 1L));
|
() -> createRolloutWithStartAt(rolloutName + "_withStartTime", filter, distributionSet, 1L));
|
||||||
// same action succeeds with system role permission
|
// same action succeeds with system role permission
|
||||||
SecurityContextSwitch.runAs(
|
SecurityContextSwitch.getAs(
|
||||||
userWithSystemRole,
|
userWithSystemRole,
|
||||||
() -> createRolloutWithStartAt(rolloutName + "_withStartTimeSystemRole", filter, distributionSet, 1L));
|
() -> createRolloutWithStartAt(rolloutName + "_withStartTimeSystemRole", filter, distributionSet, 1L));
|
||||||
// same action succeeds without handle rollout permission but with null start at
|
// same action succeeds without handle rollout permission but with null start at
|
||||||
SecurityContextSwitch.runAs(
|
SecurityContextSwitch.getAs(
|
||||||
userWithoutHandleRollout,
|
userWithoutHandleRollout,
|
||||||
() -> createRolloutWithStartAt(rolloutName + "_withoutStartTime", filter, distributionSet, null));
|
() -> createRolloutWithStartAt(rolloutName + "_withoutStartTime", filter, distributionSet, null));
|
||||||
// same action succeeds without handle rollout permission but with Long.MAX_VALUE start at
|
// same action succeeds without handle rollout permission but with Long.MAX_VALUE start at
|
||||||
SecurityContextSwitch.runAs(
|
SecurityContextSwitch.getAs(
|
||||||
userWithoutHandleRollout,
|
userWithoutHandleRollout,
|
||||||
() -> createRolloutWithStartAt(rolloutName + "_withLongMax", filter, distributionSet, Long.MAX_VALUE));
|
() -> createRolloutWithStartAt(rolloutName + "_withLongMax", filter, distributionSet, Long.MAX_VALUE));
|
||||||
}
|
}
|
||||||
@@ -2501,7 +2501,7 @@ class RolloutManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
.pollInterval(Duration.ofMillis(500))
|
.pollInterval(Duration.ofMillis(500))
|
||||||
.atMost(Duration.ofSeconds(10))
|
.atMost(Duration.ofSeconds(10))
|
||||||
.until(() -> SecurityContextSwitch
|
.until(() -> SecurityContextSwitch
|
||||||
.runAsPrivileged(
|
.callAsPrivileged(
|
||||||
() -> rolloutManagement.get(myRolloutId).orElseThrow(NoSuchElementException::new))
|
() -> rolloutManagement.get(myRolloutId).orElseThrow(NoSuchElementException::new))
|
||||||
.getStatus().equals(RolloutStatus.RUNNING));
|
.getStatus().equals(RolloutStatus.RUNNING));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ class SystemManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
|
|
||||||
for (int i = 0; i < tenants; i++) {
|
for (int i = 0; i < tenants; i++) {
|
||||||
final String tenantname = "TENANT" + i;
|
final String tenantname = "TENANT" + i;
|
||||||
SecurityContextSwitch.runAs(SecurityContextSwitch.withUserAndTenant("bumlux", tenantname, true, true, false,
|
SecurityContextSwitch.getAs(SecurityContextSwitch.withUserAndTenant("bumlux", tenantname, true, true, false,
|
||||||
SpringEvalExpressions.SYSTEM_ROLE), () -> {
|
SpringEvalExpressions.SYSTEM_ROLE), () -> {
|
||||||
systemManagement.getTenantMetadataWithoutDetails();
|
systemManagement.getTenantMetadataWithoutDetails();
|
||||||
if (artifactSize > 0) {
|
if (artifactSize > 0) {
|
||||||
|
|||||||
@@ -167,15 +167,15 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
.create(entityFactory.target().create().controllerId("targetWithSecurityToken").securityToken("token"));
|
.create(entityFactory.target().create().controllerId("targetWithSecurityToken").securityToken("token"));
|
||||||
|
|
||||||
// retrieve security token only with READ_TARGET_SEC_TOKEN permission
|
// retrieve security token only with READ_TARGET_SEC_TOKEN permission
|
||||||
final String securityTokenWithReadPermission = SecurityContextSwitch.runAs(
|
final String securityTokenWithReadPermission = SecurityContextSwitch.getAs(
|
||||||
SecurityContextSwitch.withUser("OnlyTargetReadPermission", SpPermission.READ_TARGET_SEC_TOKEN),
|
SecurityContextSwitch.withUser("OnlyTargetReadPermission", SpPermission.READ_TARGET_SEC_TOKEN),
|
||||||
createdTarget::getSecurityToken);
|
createdTarget::getSecurityToken);
|
||||||
// retrieve security token only with ROLE_TARGET_ADMIN permission
|
// retrieve security token only with ROLE_TARGET_ADMIN permission
|
||||||
final String securityTokenWithTargetAdminPermission = SecurityContextSwitch.runAs(
|
final String securityTokenWithTargetAdminPermission = SecurityContextSwitch.getAs(
|
||||||
SecurityContextSwitch.withUser("OnlyTargetAdminPermission", SpRole.TARGET_ADMIN),
|
SecurityContextSwitch.withUser("OnlyTargetAdminPermission", SpRole.TARGET_ADMIN),
|
||||||
createdTarget::getSecurityToken);
|
createdTarget::getSecurityToken);
|
||||||
// retrieve security token only with ROLE_TENANT_ADMIN permission
|
// retrieve security token only with ROLE_TENANT_ADMIN permission
|
||||||
final String securityTokenWithTenantAdminPermission = SecurityContextSwitch.runAs(
|
final String securityTokenWithTenantAdminPermission = SecurityContextSwitch.getAs(
|
||||||
SecurityContextSwitch.withUser("OnlyTenantAdminPermission", SpRole.TENANT_ADMIN),
|
SecurityContextSwitch.withUser("OnlyTenantAdminPermission", SpRole.TENANT_ADMIN),
|
||||||
createdTarget::getSecurityToken);
|
createdTarget::getSecurityToken);
|
||||||
|
|
||||||
@@ -184,7 +184,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
|
|
||||||
// retrieve security token without any permissions
|
// retrieve security token without any permissions
|
||||||
final String securityTokenWithoutPermission = SecurityContextSwitch
|
final String securityTokenWithoutPermission = SecurityContextSwitch
|
||||||
.runAs(SecurityContextSwitch.withUser("NoPermission"), createdTarget::getSecurityToken);
|
.getAs(SecurityContextSwitch.withUser("NoPermission"), createdTarget::getSecurityToken);
|
||||||
|
|
||||||
assertThat(createdTarget.getSecurityToken()).isEqualTo("token");
|
assertThat(createdTarget.getSecurityToken()).isEqualTo("token");
|
||||||
assertThat(securityTokenWithReadPermission).isNotNull();
|
assertThat(securityTokenWithReadPermission).isNotNull();
|
||||||
@@ -705,7 +705,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
|
|||||||
final String knownTargetControllerId = "readTarget";
|
final String knownTargetControllerId = "readTarget";
|
||||||
controllerManagement.findOrRegisterTargetIfItDoesNotExist(knownTargetControllerId, new URI("http://127.0.0.1"));
|
controllerManagement.findOrRegisterTargetIfItDoesNotExist(knownTargetControllerId, new URI("http://127.0.0.1"));
|
||||||
|
|
||||||
SecurityContextSwitch.runAs(SecurityContextSwitch.withUser("bumlux", "READ_TARGET"), () -> {
|
SecurityContextSwitch.getAs(SecurityContextSwitch.withUser("bumlux", "READ_TARGET"), () -> {
|
||||||
final Target findTargetByControllerID = targetManagement.getByControllerID(knownTargetControllerId)
|
final Target findTargetByControllerID = targetManagement.getByControllerID(knownTargetControllerId)
|
||||||
.orElseThrow(IllegalStateException::new);
|
.orElseThrow(IllegalStateException::new);
|
||||||
assertThat(findTargetByControllerID).isNotNull();
|
assertThat(findTargetByControllerID).isNotNull();
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import jakarta.persistence.PersistenceContext;
|
|||||||
|
|
||||||
import org.eclipse.hawkbit.repository.RsqlQueryField;
|
import org.eclipse.hawkbit.repository.RsqlQueryField;
|
||||||
import org.eclipse.hawkbit.repository.TargetFields;
|
import org.eclipse.hawkbit.repository.TargetFields;
|
||||||
import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration;
|
import org.eclipse.hawkbit.repository.jpa.JpaRepositoryConfiguration;
|
||||||
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
|
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
|
||||||
import org.eclipse.hawkbit.repository.jpa.ql.utils.HawkbitQlToSql;
|
import org.eclipse.hawkbit.repository.jpa.ql.utils.HawkbitQlToSql;
|
||||||
import org.eclipse.hawkbit.repository.test.TestConfiguration;
|
import org.eclipse.hawkbit.repository.test.TestConfiguration;
|
||||||
@@ -35,7 +35,7 @@ import org.springframework.test.context.ContextConfiguration;
|
|||||||
"spring.main.allow-bean-definition-overriding=true",
|
"spring.main.allow-bean-definition-overriding=true",
|
||||||
"spring.main.banner-mode=off",
|
"spring.main.banner-mode=off",
|
||||||
"logging.level.root=ERROR" })
|
"logging.level.root=ERROR" })
|
||||||
@ContextConfiguration(classes = { RepositoryApplicationConfiguration.class, TestConfiguration.class })
|
@ContextConfiguration(classes = { JpaRepositoryConfiguration.class, TestConfiguration.class })
|
||||||
@Disabled("For manual run only, while playing around with RSQL to SQL")
|
@Disabled("For manual run only, while playing around with RSQL to SQL")
|
||||||
@SuppressWarnings("java:S2699") // java:S2699 - manual test, don't actually does assertions
|
@SuppressWarnings("java:S2699") // java:S2699 - manual test, don't actually does assertions
|
||||||
class RsqlToSqlTest {
|
class RsqlToSqlTest {
|
||||||
|
|||||||
@@ -113,13 +113,13 @@ class MultiTenancyEntityTest extends AbstractJpaIntegrationTest {
|
|||||||
// logged in tenant mytenant - check if tenant default data is
|
// logged in tenant mytenant - check if tenant default data is
|
||||||
// autogenerated
|
// autogenerated
|
||||||
assertThat(distributionSetTypeManagement.findAll(PAGE)).isEmpty();
|
assertThat(distributionSetTypeManagement.findAll(PAGE)).isEmpty();
|
||||||
SecurityContextSwitch.runAsPrivileged(() ->
|
SecurityContextSwitch.callAsPrivileged(() ->
|
||||||
assertThat(systemManagement.createTenantMetadata("mytenant").getTenant().toUpperCase()).isEqualTo("mytenant".toUpperCase()));
|
assertThat(systemManagement.createTenantMetadata("mytenant").getTenant().toUpperCase()).isEqualTo("mytenant".toUpperCase()));
|
||||||
|
|
||||||
assertThat(distributionSetTypeManagement.findAll(PAGE)).isNotEmpty();
|
assertThat(distributionSetTypeManagement.findAll(PAGE)).isNotEmpty();
|
||||||
|
|
||||||
// check that the cache is not getting in the way, i.e. "bumlux" results in bumlux and not mytenant
|
// check that the cache is not getting in the way, i.e. "bumlux" results in bumlux and not mytenant
|
||||||
assertThat(SecurityContextSwitch.runAs(
|
assertThat(SecurityContextSwitch.getAs(
|
||||||
SecurityContextSwitch.withUserAndTenantAllSpPermissions("user", "bumlux"),
|
SecurityContextSwitch.withUserAndTenantAllSpPermissions("user", "bumlux"),
|
||||||
() -> systemManagement.getTenantMetadataWithoutDetails().getTenant().toUpperCase()))
|
() -> systemManagement.getTenantMetadataWithoutDetails().getTenant().toUpperCase()))
|
||||||
.isEqualTo("bumlux".toUpperCase());
|
.isEqualTo("bumlux".toUpperCase());
|
||||||
@@ -178,7 +178,7 @@ class MultiTenancyEntityTest extends AbstractJpaIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private <T> T runAsTenant(final String tenant, final Callable<T> callable) throws Exception {
|
private <T> T runAsTenant(final String tenant, final Callable<T> callable) throws Exception {
|
||||||
return SecurityContextSwitch.runAs(SecurityContextSwitch.withUserAndTenantAllSpPermissions("user", tenant), callable);
|
return SecurityContextSwitch.callAs(SecurityContextSwitch.withUserAndTenantAllSpPermissions("user", tenant), callable);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Target createTargetForTenant(final String controllerId, final String tenant) throws Exception {
|
private Target createTargetForTenant(final String controllerId, final String tenant) throws Exception {
|
||||||
|
|||||||
@@ -54,4 +54,7 @@ logging.level.org.eclipse.persistence=ERROR
|
|||||||
hawkbit.repository.cluster.lock.ttl=1000
|
hawkbit.repository.cluster.lock.ttl=1000
|
||||||
hawkbit.repository.cluster.lock.refreshOnRemainMS=200
|
hawkbit.repository.cluster.lock.refreshOnRemainMS=200
|
||||||
hawkbit.repository.cluster.lock.refreshOnRemainPercent=10
|
hawkbit.repository.cluster.lock.refreshOnRemainPercent=10
|
||||||
hawkbit.repository.cluster.lock.ticPeriodMS=10
|
# reduce scheduler tic period to speed up tests
|
||||||
|
hawkbit.repository.cluster.lock.ticPeriodMS=10
|
||||||
|
# disable spring cloud bus for tests
|
||||||
|
spring.cloud.bus.enabled=false
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
package org.eclipse.hawkbit.repository.test.util;
|
package org.eclipse.hawkbit.repository.test.util;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_TENANT_CONFIGURATION;
|
||||||
import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.CONTROLLER_ROLE;
|
import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.CONTROLLER_ROLE;
|
||||||
import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.SYSTEM_ROLE;
|
import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.SYSTEM_ROLE;
|
||||||
|
|
||||||
@@ -94,7 +95,6 @@ import org.springframework.test.context.TestPropertySource;
|
|||||||
@WithUser(principal = "bumlux", allSpPermissions = true, authorities = { CONTROLLER_ROLE, SYSTEM_ROLE })
|
@WithUser(principal = "bumlux", allSpPermissions = true, authorities = { CONTROLLER_ROLE, SYSTEM_ROLE })
|
||||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
|
||||||
@ContextConfiguration(classes = { TestConfiguration.class })
|
@ContextConfiguration(classes = { TestConfiguration.class })
|
||||||
//@Import(TestChannelBinderConfiguration.class)
|
|
||||||
// destroy the context after each test class because otherwise we get problem when context is
|
// destroy the context after each test class because otherwise we get problem when context is
|
||||||
// refreshed we e.g. get two instances of CacheManager which leads to very strange test failures.
|
// refreshed we e.g. get two instances of CacheManager which leads to very strange test failures.
|
||||||
@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
|
@DirtiesContext(classMode = ClassMode.AFTER_CLASS)
|
||||||
@@ -176,7 +176,7 @@ public abstract class AbstractIntegrationTest {
|
|||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
protected TestdataFactory testdataFactory;
|
protected TestdataFactory testdataFactory;
|
||||||
@Autowired
|
@Autowired(required = false)
|
||||||
protected ServiceMatcher serviceMatcher;
|
protected ServiceMatcher serviceMatcher;
|
||||||
@Autowired
|
@Autowired
|
||||||
protected ApplicationEventPublisher eventPublisher;
|
protected ApplicationEventPublisher eventPublisher;
|
||||||
@@ -203,21 +203,21 @@ public abstract class AbstractIntegrationTest {
|
|||||||
final String description = "Updated description.";
|
final String description = "Updated description.";
|
||||||
|
|
||||||
osType = SecurityContextSwitch
|
osType = SecurityContextSwitch
|
||||||
.runAsPrivileged(() -> testdataFactory.findOrCreateSoftwareModuleType(TestdataFactory.SM_TYPE_OS));
|
.callAsPrivileged(() -> testdataFactory.findOrCreateSoftwareModuleType(TestdataFactory.SM_TYPE_OS));
|
||||||
osType = SecurityContextSwitch.runAsPrivileged(() -> softwareModuleTypeManagement
|
osType = SecurityContextSwitch.callAsPrivileged(() -> softwareModuleTypeManagement
|
||||||
.update(entityFactory.softwareModuleType().update(osType.getId()).description(description)));
|
.update(entityFactory.softwareModuleType().update(osType.getId()).description(description)));
|
||||||
|
|
||||||
appType = SecurityContextSwitch.runAsPrivileged(
|
appType = SecurityContextSwitch.callAsPrivileged(
|
||||||
() -> testdataFactory.findOrCreateSoftwareModuleType(TestdataFactory.SM_TYPE_APP, Integer.MAX_VALUE));
|
() -> testdataFactory.findOrCreateSoftwareModuleType(TestdataFactory.SM_TYPE_APP, Integer.MAX_VALUE));
|
||||||
appType = SecurityContextSwitch.runAsPrivileged(() -> softwareModuleTypeManagement
|
appType = SecurityContextSwitch.callAsPrivileged(() -> softwareModuleTypeManagement
|
||||||
.update(entityFactory.softwareModuleType().update(appType.getId()).description(description)));
|
.update(entityFactory.softwareModuleType().update(appType.getId()).description(description)));
|
||||||
|
|
||||||
runtimeType = SecurityContextSwitch
|
runtimeType = SecurityContextSwitch
|
||||||
.runAsPrivileged(() -> testdataFactory.findOrCreateSoftwareModuleType(TestdataFactory.SM_TYPE_RT));
|
.callAsPrivileged(() -> testdataFactory.findOrCreateSoftwareModuleType(TestdataFactory.SM_TYPE_RT));
|
||||||
runtimeType = SecurityContextSwitch.runAsPrivileged(() -> softwareModuleTypeManagement
|
runtimeType = SecurityContextSwitch.callAsPrivileged(() -> softwareModuleTypeManagement
|
||||||
.update(entityFactory.softwareModuleType().update(runtimeType.getId()).description(description)));
|
.update(entityFactory.softwareModuleType().update(runtimeType.getId()).description(description)));
|
||||||
|
|
||||||
standardDsType = SecurityContextSwitch.runAsPrivileged(() -> testdataFactory.findOrCreateDefaultTestDsType());
|
standardDsType = SecurityContextSwitch.callAsPrivileged(() -> testdataFactory.findOrCreateDefaultTestDsType());
|
||||||
|
|
||||||
// publish the reset counter market event to reset the counters after
|
// publish the reset counter market event to reset the counters after
|
||||||
// setup. The setup is transparent by the test and its @ExpectedEvent
|
// setup. The setup is transparent by the test and its @ExpectedEvent
|
||||||
@@ -267,6 +267,7 @@ public abstract class AbstractIntegrationTest {
|
|||||||
private static final Duration AT_LEAST = Duration.ofMillis(Integer.getInteger("hawkbit.it.rest.await.atLeastMs", 5));
|
private static final Duration AT_LEAST = Duration.ofMillis(Integer.getInteger("hawkbit.it.rest.await.atLeastMs", 5));
|
||||||
private static final Duration POLL_INTERVAL = Duration.ofMillis(Integer.getInteger("hawkbit.it.rest.await.pollIntervalMs", 10));
|
private static final Duration POLL_INTERVAL = Duration.ofMillis(Integer.getInteger("hawkbit.it.rest.await.pollIntervalMs", 10));
|
||||||
private static final Duration TIMEOUT = Duration.ofMillis(Integer.getInteger("hawkbit.it.rest.await.timeoutMs", 200));
|
private static final Duration TIMEOUT = Duration.ofMillis(Integer.getInteger("hawkbit.it.rest.await.timeoutMs", 200));
|
||||||
|
|
||||||
// default wait condition factory
|
// default wait condition factory
|
||||||
protected ConditionFactory await() {
|
protected ConditionFactory await() {
|
||||||
return Awaitility.await().atLeast(AT_LEAST).pollInterval(POLL_INTERVAL).atMost(TIMEOUT);
|
return Awaitility.await().atLeast(AT_LEAST).pollInterval(POLL_INTERVAL).atMost(TIMEOUT);
|
||||||
@@ -413,7 +414,11 @@ public abstract class AbstractIntegrationTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isConfirmationFlowActive() {
|
protected boolean isConfirmationFlowActive() {
|
||||||
return tenantConfigurationManagement.getConfigurationValue(TenantConfigurationKey.USER_CONFIRMATION_ENABLED, Boolean.class).getValue();
|
return SecurityContextSwitch.getAs(
|
||||||
|
SecurityContextSwitch.withUser("as_system", READ_TENANT_CONFIGURATION),
|
||||||
|
() -> tenantConfigurationManagement
|
||||||
|
.getConfigurationValue(TenantConfigurationKey.USER_CONFIRMATION_ENABLED, Boolean.class)
|
||||||
|
.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Long getOsModule(final DistributionSet ds) {
|
protected Long getOsModule(final DistributionSet ds) {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public class CleanupTestExecutionListener extends AbstractTestExecutionListener
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterTestMethod(final TestContext testContext) throws Exception {
|
public void afterTestMethod(final TestContext testContext) throws Exception {
|
||||||
SecurityContextSwitch.runAsPrivileged(() -> {
|
SecurityContextSwitch.callAsPrivileged(() -> {
|
||||||
final ApplicationContext applicationContext = testContext.getApplicationContext();
|
final ApplicationContext applicationContext = testContext.getApplicationContext();
|
||||||
new JpaTestRepositoryManagement(applicationContext.getBean(TenantAwareCacheManager.class),
|
new JpaTestRepositoryManagement(applicationContext.getBean(TenantAwareCacheManager.class),
|
||||||
applicationContext.getBean(SystemSecurityContext.class),
|
applicationContext.getBean(SystemSecurityContext.class),
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
@@ -35,12 +36,12 @@ public class SecurityContextSwitch {
|
|||||||
private static final WithUser PRIVILEDGED_USER =
|
private static final WithUser PRIVILEDGED_USER =
|
||||||
createWithUser("bumlux", DEFAULT_TENANT, false, true, false, "ROLE_CONTROLLER", "ROLE_SYSTEM_CODE");
|
createWithUser("bumlux", DEFAULT_TENANT, false, true, false, "ROLE_CONTROLLER", "ROLE_SYSTEM_CODE");
|
||||||
|
|
||||||
public static <T> T runAsPrivileged(final Callable<T> callable) throws Exception {
|
public static <T> T callAsPrivileged(final Callable<T> callable) throws Exception {
|
||||||
createTenant(DEFAULT_TENANT);
|
createTenant(DEFAULT_TENANT);
|
||||||
return runAs(PRIVILEDGED_USER, callable);
|
return callAs(PRIVILEDGED_USER, callable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> T runAs(final WithUser withUser, final Callable<T> callable) throws Exception {
|
public static <T> T callAs(final WithUser withUser, final Callable<T> callable) throws Exception {
|
||||||
final SecurityContext oldContext = SecurityContextHolder.getContext();
|
final SecurityContext oldContext = SecurityContextHolder.getContext();
|
||||||
setSecurityContext(withUser);
|
setSecurityContext(withUser);
|
||||||
if (withUser.autoCreateTenant()) {
|
if (withUser.autoCreateTenant()) {
|
||||||
@@ -53,6 +54,23 @@ public class SecurityContextSwitch {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> T getAs(final WithUser withUser, final Supplier<T> supplier) {
|
||||||
|
try {
|
||||||
|
return callAs(withUser, supplier::get);
|
||||||
|
} catch (final RuntimeException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (final Exception e) {
|
||||||
|
throw new IllegalStateException("Failed to handle all rollouts", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void runAs(final WithUser withUser, final Runnable runnable) {
|
||||||
|
getAs(withUser, (Supplier<? extends Object>) () -> {
|
||||||
|
runnable.run();
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public static WithUser withController(final String principal, final String... authorities) {
|
public static WithUser withController(final String principal, final String... authorities) {
|
||||||
return withUserAndTenant(principal, DEFAULT_TENANT, true, false, true, authorities);
|
return withUserAndTenant(principal, DEFAULT_TENANT, true, false, true, authorities);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
package org.eclipse.hawkbit.repository.test.util;
|
package org.eclipse.hawkbit.repository.test.util;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.SYSTEM_ROLE;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -1018,7 +1019,7 @@ public class TestdataFactory {
|
|||||||
groupSize, confirmationRequired, conditions, dynamicRolloutGroupTemplate);
|
groupSize, confirmationRequired, conditions, dynamicRolloutGroupTemplate);
|
||||||
|
|
||||||
// Run here, because Scheduler is disabled during tests
|
// Run here, because Scheduler is disabled during tests
|
||||||
rolloutHandler.handleAll();
|
rolloutHandleAll();
|
||||||
|
|
||||||
return rolloutManagement.get(rollout.getId()).get();
|
return rolloutManagement.get(rollout.getId()).get();
|
||||||
}
|
}
|
||||||
@@ -1127,9 +1128,9 @@ public class TestdataFactory {
|
|||||||
public Rollout createSoftDeletedRollout(final String prefix) {
|
public Rollout createSoftDeletedRollout(final String prefix) {
|
||||||
final Rollout newRollout = createRollout(prefix);
|
final Rollout newRollout = createRollout(prefix);
|
||||||
rolloutManagement.start(newRollout.getId());
|
rolloutManagement.start(newRollout.getId());
|
||||||
rolloutHandler.handleAll();
|
rolloutHandleAll();
|
||||||
rolloutManagement.delete(newRollout.getId());
|
rolloutManagement.delete(newRollout.getId());
|
||||||
rolloutHandler.handleAll();
|
rolloutHandleAll();
|
||||||
return newRollout;
|
return newRollout;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1247,17 +1248,20 @@ public class TestdataFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Action sendUpdateActionStatusToTarget(final Status status, final Action updActA, final Collection<String> msgs) {
|
private Action sendUpdateActionStatusToTarget(final Status status, final Action updActA, final Collection<String> msgs) {
|
||||||
return controllerManagement.addUpdateActionStatus(
|
return controllerManagement.addUpdateActionStatus(entityFactory.actionStatus().create(updActA.getId()).status(status).messages(msgs));
|
||||||
entityFactory.actionStatus().create(updActA.getId()).status(status).messages(msgs));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Rollout startAndReloadRollout(final Rollout rollout) {
|
private Rollout startAndReloadRollout(final Rollout rollout) {
|
||||||
rolloutManagement.start(rollout.getId());
|
rolloutManagement.start(rollout.getId());
|
||||||
// Run here, because scheduler is disabled during tests
|
// Run here, because scheduler is disabled during tests
|
||||||
rolloutHandler.handleAll();
|
rolloutHandleAll();
|
||||||
return reloadRollout(rollout);
|
return reloadRollout(rollout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void rolloutHandleAll() {
|
||||||
|
SecurityContextSwitch.runAs(SecurityContextSwitch.withUser("system", SYSTEM_ROLE), rolloutHandler::handleAll);
|
||||||
|
}
|
||||||
|
|
||||||
private Rollout reloadRollout(final Rollout rollout) {
|
private Rollout reloadRollout(final Rollout rollout) {
|
||||||
return rolloutManagement.get(rollout.getId()).orElseThrow(NoSuchElementException::new);
|
return rolloutManagement.get(rollout.getId()).orElseThrow(NoSuchElementException::new);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,3 +79,6 @@ hawkbit.server.security.dos.maxActionsPerTarget=20
|
|||||||
# Quota - END
|
# Quota - END
|
||||||
|
|
||||||
# Properties that are managed by autoconfigure module at runtime and not available during test - END
|
# Properties that are managed by autoconfigure module at runtime and not available during test - END
|
||||||
|
|
||||||
|
# disable spring cloud bus for tests
|
||||||
|
spring.cloud.bus.enabled=false
|
||||||
@@ -9,9 +9,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.rest;
|
package org.eclipse.hawkbit.rest;
|
||||||
|
|
||||||
import java.time.Duration;
|
import org.eclipse.hawkbit.repository.jpa.JpaRepositoryConfiguration;
|
||||||
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration;
|
|
||||||
import org.eclipse.hawkbit.repository.test.TestConfiguration;
|
import org.eclipse.hawkbit.repository.test.TestConfiguration;
|
||||||
import org.eclipse.hawkbit.repository.test.util.AbstractIntegrationTest;
|
import org.eclipse.hawkbit.repository.test.util.AbstractIntegrationTest;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
@@ -31,7 +29,7 @@ import org.springframework.web.filter.CharacterEncodingFilter;
|
|||||||
*/
|
*/
|
||||||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
|
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
|
||||||
@ContextConfiguration(classes = {
|
@ContextConfiguration(classes = {
|
||||||
RestConfiguration.class, RepositoryApplicationConfiguration.class, TestConfiguration.class })
|
RestConfiguration.class, JpaRepositoryConfiguration.class, TestConfiguration.class })
|
||||||
@WebAppConfiguration
|
@WebAppConfiguration
|
||||||
@AutoConfigureMockMvc
|
@AutoConfigureMockMvc
|
||||||
public abstract class AbstractRestIntegrationTest extends AbstractIntegrationTest {
|
public abstract class AbstractRestIntegrationTest extends AbstractIntegrationTest {
|
||||||
|
|||||||
@@ -400,8 +400,7 @@ public final class SpPermission {
|
|||||||
* context contains the anonymous role or the controller specific role
|
* context contains the anonymous role or the controller specific role
|
||||||
* {@link SpringEvalExpressions#CONTROLLER_ROLE}.
|
* {@link SpringEvalExpressions#CONTROLLER_ROLE}.
|
||||||
*/
|
*/
|
||||||
public static final String IS_CONTROLLER = "hasAnyRole('" + CONTROLLER_ROLE_ANONYMOUS + "', '" + CONTROLLER_ROLE
|
public static final String IS_CONTROLLER = "hasAnyRole('" + CONTROLLER_ROLE_ANONYMOUS + "', '" + CONTROLLER_ROLE + "')";
|
||||||
+ "')";
|
|
||||||
/**
|
/**
|
||||||
* Spring security eval hasAuthority expression to check if spring
|
* Spring security eval hasAuthority expression to check if spring
|
||||||
* context contains {@link #IS_CONTROLLER} or
|
* context contains {@link #IS_CONTROLLER} or
|
||||||
|
|||||||
Reference in New Issue
Block a user