[#1712] Introduce READ_TENANT_CONFIGURATION permission (#1713)

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-04-12 14:30:29 +03:00
committed by GitHub
parent 3497d155a1
commit 3611a8eccd
10 changed files with 127 additions and 93 deletions

View File

@@ -89,7 +89,7 @@ public interface SystemManagement {
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.HAS_AUTH_READ_TARGET + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION_READ + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_CONTROLLER)
TenantMetaData getTenantMetadata();

View File

@@ -62,22 +62,6 @@ public interface TenantConfigurationManagement {
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION)
<T extends Serializable> Map<String, TenantConfigurationValue<T>> addOrUpdateConfiguration(Map<String, T> configurations);
/**
* Build the tenant configuration by the given key
*
* @param configurationKey
* the key
* @param propertyType
* the property type
* @param tenantConfiguration
* the configuration
* @return <null> if no default value is set and no database value available
* or returns the tenant configuration value
*/
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION)
<T extends Serializable> TenantConfigurationValue<T> buildTenantConfigurationValueByKey(
TenantConfigurationKey configurationKey, Class<T> propertyType, TenantConfiguration tenantConfiguration);
/**
* Deletes a specific configuration for the current tenant. Does nothing in
* case there is no tenant specific configuration value.
@@ -106,7 +90,7 @@ public interface TenantConfigurationManagement {
* if the property cannot be converted to the given
* {@code propertyType}
*/
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION)
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION_READ)
<T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(String configurationKeyName);
/**
@@ -132,7 +116,7 @@ public interface TenantConfigurationManagement {
* if the property cannot be converted to the given
* {@code propertyType}
*/
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION)
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION_READ)
<T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(String configurationKeyName,
Class<T> propertyType);
@@ -156,6 +140,6 @@ public interface TenantConfigurationManagement {
* if the property cannot be converted to the given
* {@code propertyType}
*/
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION)
@PreAuthorize(value = SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION_READ)
<T> T getGlobalConfigurationValue(String configurationKeyName, Class<T> propertyType);
}

View File

@@ -28,7 +28,7 @@ public interface TenantStatsManagement {
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.HAS_AUTH_READ_TARGET + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION_READ + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.IS_SYSTEM_CODE)
TenantUsage getStatsOfTenant();

View File

@@ -21,7 +21,9 @@ import java.util.Map;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.repository.exception.TenantConfigurationValueChangeNotAllowedException;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
@@ -29,6 +31,8 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTenantConfiguration;
import org.eclipse.hawkbit.repository.jpa.repository.TenantConfigurationRepository;
import org.eclipse.hawkbit.repository.model.TenantConfiguration;
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
import org.eclipse.hawkbit.repository.model.helper.SystemSecurityContextHolder;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationValidatorException;
@@ -75,6 +79,7 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
@Cacheable(value = "tenantConfiguration", key = "#configurationKeyName")
public <T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(final String configurationKeyName,
final Class<T> propertyType) {
checkAccess(configurationKeyName);
final TenantConfigurationKey configurationKey = tenantConfigurationProperties.fromKeyName(configurationKeyName);
@@ -86,48 +91,11 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
return buildTenantConfigurationValueByKey(configurationKey, propertyType, tenantConfiguration);
}
/**
* Validates the data type of the tenant configuration. If it is possible to
* cast to the given data type.
*
* @param configurationKey
* the key
* @param propertyType
* the class
*/
static <T> void validateTenantConfigurationDataType(final TenantConfigurationKey configurationKey,
final Class<T> propertyType) {
if (!configurationKey.getDataType().isAssignableFrom(propertyType)) {
throw new TenantConfigurationValidatorException(
String.format("Cannot parse the database value of type %s into the type %s.",
configurationKey.getDataType(), propertyType));
}
}
@Override
public <T extends Serializable> TenantConfigurationValue<T> buildTenantConfigurationValueByKey(
final TenantConfigurationKey configurationKey, final Class<T> propertyType,
final TenantConfiguration tenantConfiguration) {
if (tenantConfiguration != null) {
return TenantConfigurationValue.<T> builder().global(false).createdBy(tenantConfiguration.getCreatedBy())
.createdAt(tenantConfiguration.getCreatedAt())
.lastModifiedAt(tenantConfiguration.getLastModifiedAt())
.lastModifiedBy(tenantConfiguration.getLastModifiedBy())
.value(conversionService.convert(tenantConfiguration.getValue(), propertyType)).build();
} else if (configurationKey.getDefaultValue() != null) {
return TenantConfigurationValue.<T> builder().global(true).createdBy(null).createdAt(null)
.lastModifiedAt(null).lastModifiedBy(null)
.value(getGlobalConfigurationValue(configurationKey.getKeyName(), propertyType)).build();
}
return null;
}
@Override
public <T extends Serializable> TenantConfigurationValue<T> getConfigurationValue(
final String configurationKeyName) {
checkAccess(configurationKeyName);
final TenantConfigurationKey configurationKey = tenantConfigurationProperties.fromKeyName(configurationKeyName);
return getConfigurationValue(configurationKeyName, (Class<T>)configurationKey.getDataType());
@@ -135,6 +103,7 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
@Override
public <T> T getGlobalConfigurationValue(final String configurationKeyName, final Class<T> propertyType) {
checkAccess(configurationKeyName);
final TenantConfigurationKey key = tenantConfigurationProperties.fromKeyName(configurationKeyName);
@@ -146,6 +115,19 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
return conversionService.convert(key.getDefaultValue(), propertyType);
}
private void checkAccess(final String configurationKeyName) {
if (TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_MODE_GATEWAY_SECURITY_TOKEN_KEY
.equalsIgnoreCase(configurationKeyName)) {
final SystemSecurityContext systemSecurityContext =
SystemSecurityContextHolder.getInstance().getSystemSecurityContext();
if (!systemSecurityContext.isCurrentThreadSystemCode() &&
!systemSecurityContext.hasPermission(SpPermission.READ_GATEWAY_SEC_TOKEN)) {
throw new InsufficientPermissionException(
"Can't read gateway security token! " + SpPermission.READ_GATEWAY_SEC_TOKEN + " is required!");
}
}
}
@Override
@CacheEvict(value = "tenantConfiguration", key = "#configurationKeyName")
@Transactional
@@ -217,6 +199,51 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
}));
}
@Override
@CacheEvict(value = "tenantConfiguration", key = "#configurationKeyName")
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void deleteConfiguration(final String configurationKeyName) {
tenantConfigurationRepository.deleteByKey(configurationKeyName);
}
/**
* Validates the data type of the tenant configuration. If it is possible to
* cast to the given data type.
*
* @param configurationKey the key
* @param propertyType the class
*/
private static <T> void validateTenantConfigurationDataType(final TenantConfigurationKey configurationKey,
final Class<T> propertyType) {
if (!configurationKey.getDataType().isAssignableFrom(propertyType)) {
throw new TenantConfigurationValidatorException(
String.format("Cannot parse the database value of type %s into the type %s.",
configurationKey.getDataType(), propertyType));
}
}
private <T extends Serializable> TenantConfigurationValue<T> buildTenantConfigurationValueByKey(
final TenantConfigurationKey configurationKey, final Class<T> propertyType,
final TenantConfiguration tenantConfiguration) {
if (tenantConfiguration != null) {
return TenantConfigurationValue.<T> builder().global(false).createdBy(tenantConfiguration.getCreatedBy())
.createdAt(tenantConfiguration.getCreatedAt())
.lastModifiedAt(tenantConfiguration.getLastModifiedAt())
.lastModifiedBy(tenantConfiguration.getLastModifiedBy())
.value(conversionService.convert(tenantConfiguration.getValue(), propertyType)).build();
} else if (configurationKey.getDefaultValue() != null) {
return TenantConfigurationValue.<T> builder().global(true).createdBy(null).createdAt(null)
.lastModifiedAt(null).lastModifiedBy(null)
.value(getGlobalConfigurationValue(configurationKey.getKeyName(), propertyType)).build();
}
return null;
}
/**
* Asserts that the requested configuration value change is allowed. Throws
* a {@link TenantConfigurationValueChangeNotAllowedException} otherwise.
@@ -271,13 +298,4 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
}
}
}
@Override
@CacheEvict(value = "tenantConfiguration", key = "#configurationKeyName")
@Transactional
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void deleteConfiguration(final String configurationKeyName) {
tenantConfigurationRepository.deleteByKey(configurationKeyName);
}
}

View File

@@ -12,7 +12,7 @@ package org.eclipse.hawkbit.ddi.rest.resource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.CONTROLLER_ROLE;
import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.CONTROLLER_ROLE_ANONYMOUS;
import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION;
import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.HAS_AUTH_TENANT_CONFIGURATION_READ;
import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.SYSTEM_ROLE;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.Matchers.containsString;
@@ -210,7 +210,7 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
@Expect(type = TargetPollEvent.class, count = 1),
@Expect(type = TenantConfigurationCreatedEvent.class, count = 1) })
void pollWithModifiedGlobalPollingTime() throws Exception {
SecurityContextSwitch.runAs(SecurityContextSwitch.withUser("tenantadmin", HAS_AUTH_TENANT_CONFIGURATION),
SecurityContextSwitch.runAs(SecurityContextSwitch.withUser("tenantadmin", HAS_AUTH_TENANT_CONFIGURATION_READ),
() -> {
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.POLLING_TIME_INTERVAL,
"00:02:00");
@@ -611,7 +611,7 @@ class DdiRootControllerTest extends AbstractDDiApiIntegrationTest {
void sleepTimeResponseForDifferentMaintenanceWindowParameters() throws Exception {
final DistributionSet ds = testdataFactory.createDistributionSet("");
SecurityContextSwitch.runAs(SecurityContextSwitch.withUser("tenantadmin", HAS_AUTH_TENANT_CONFIGURATION),
SecurityContextSwitch.runAs(SecurityContextSwitch.withUser("tenantadmin", HAS_AUTH_TENANT_CONFIGURATION_READ),
() -> {
tenantConfigurationManagement.addOrUpdateConfiguration(TenantConfigurationKey.POLLING_TIME_INTERVAL,
"00:05:00");

View File

@@ -45,7 +45,7 @@ public interface MgmtTenantManagementRestApi {
* @return a map of all configuration values.
*/
@Operation(summary = "Return all tenant specific configuration values", description = "The GET request returns " +
"a list of all possible configuration keys for the tenant. Required Permission: TENANT_CONFIGURATION")
"a list of all possible configuration keys for the tenant. Required Permission: READ_TENANT_CONFIGURATION")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successfully retrieved"),
@ApiResponse(responseCode = "400", description = "Bad Request - e.g. invalid parameters",
@@ -115,7 +115,7 @@ public interface MgmtTenantManagementRestApi {
*/
@Operation(summary = "Return a tenant specific configuration value", description = "The GET request returns the " +
"configuration value of a specific configuration key for the tenant. " +
"Required Permission: TENANT_CONFIGURATION")
"Required Permission: READ_TENANT_CONFIGURATION")
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "Successfully retrieved"),
@ApiResponse(responseCode = "400", description = "Bad Request - e.g. invalid parameters",

View File

@@ -10,6 +10,7 @@
package org.eclipse.hawkbit.mgmt.rest.resource;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -21,6 +22,7 @@ import org.eclipse.hawkbit.mgmt.json.model.system.MgmtSystemTenantConfigurationV
import org.eclipse.hawkbit.mgmt.rest.api.MgmtTenantManagementRestApi;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
import org.eclipse.hawkbit.tenancy.configuration.validator.TenantConfigurationValidatorException;
@@ -51,14 +53,21 @@ public class MgmtTenantManagementResource implements MgmtTenantManagementRestApi
@Override
public ResponseEntity<Map<String, MgmtSystemTenantConfigurationValue>> getTenantConfiguration() {
//Load and Construct default Tenant Configuration
Map<String, MgmtSystemTenantConfigurationValue> tenantConfigurationValueMap = tenantConfigurationProperties.getConfigurationKeys().stream().collect(
Collectors.toMap(TenantConfigurationProperties.TenantConfigurationKey::getKeyName,
key -> loadTenantConfigurationValueBy(key.getKeyName())));
//Load and Add Default DistributionSetType
MgmtSystemTenantConfigurationValue defaultDsTypeId = loadTenantConfigurationValueBy(MgmtTenantManagementMapper.DEFAULT_DISTRIBUTION_SET_TYPE_KEY);
// Load and Construct default Tenant Configuration
final Map<String, MgmtSystemTenantConfigurationValue> tenantConfigurationValueMap = new HashMap<>();
tenantConfigurationProperties.getConfigurationKeys().forEach(key -> {
try {
tenantConfigurationValueMap.put(key.getKeyName(), loadTenantConfigurationValueBy(key.getKeyName()));
} catch (final InsufficientPermissionException e) {
// some values as gateway token may not be accessibly for the caller - just skip them
}
});
// Load and Add Default DistributionSetType
final MgmtSystemTenantConfigurationValue defaultDsTypeId = loadTenantConfigurationValueBy(MgmtTenantManagementMapper.DEFAULT_DISTRIBUTION_SET_TYPE_KEY);
tenantConfigurationValueMap.put(MgmtTenantManagementMapper.DEFAULT_DISTRIBUTION_SET_TYPE_KEY, defaultDsTypeId);
//return combined TenantConfiguration and TenantMetadata
// return combined TenantConfiguration and TenantMetadata
log.debug("getTenantConfiguration, return status {}", HttpStatus.OK);
return ResponseEntity.ok(tenantConfigurationValueMap);
}

View File

@@ -14,6 +14,8 @@ import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.GrantedAuthority;
@@ -31,6 +33,7 @@ import org.springframework.security.core.GrantedAuthority;
* etc.
* </p>
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Slf4j
public final class SpPermission {
@@ -93,6 +96,19 @@ public final class SpPermission {
*/
public static final String DOWNLOAD_REPOSITORY_ARTIFACT = "DOWNLOAD_REPOSITORY_ARTIFACT";
/**
* Permission to read the tenant settings.
*/
public static final String READ_TENANT_CONFIGURATION = "READ_TENANT_CONFIGURATION";
/**
* Permission to read the gateway security token. The gateway security token is security
* concerned and should be protected. So in addition to {@linkplain #READ_TENANT_CONFIGURATION},
* {@code READ_GATEWAY_SEC_TOKEN} is necessary to read gateway security token. {@link #TENANT_CONFIGURATION}
* implies both permissions - so it is sufficient to read the gateway security token.
*/
public static final String READ_GATEWAY_SEC_TOKEN = "READ_GATEWAY_SECURITY_TOKEN";
/**
* Permission to administrate the tenant settings.
*/
@@ -128,10 +144,6 @@ public final class SpPermission {
*/
public static final String APPROVE_ROLLOUT = "APPROVE_ROLLOUT";
private SpPermission() {
// Constants only
}
/**
* Return all permission.
* @return all permissions
@@ -175,6 +187,7 @@ public final class SpPermission {
* }
* </p>
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public static final class SpringEvalExpressions {
/*
* Spring security eval expressions.
@@ -404,6 +417,14 @@ public final class SpPermission {
public static final String HAS_AUTH_ROLLOUT_MANAGEMENT_DELETE = HAS_AUTH_PREFIX + DELETE_ROLLOUT
+ HAS_AUTH_SUFFIX + HAS_AUTH_OR + IS_SYSTEM_CODE;
/**
* Spring security eval hasAuthority expression to check if spring
* context contains {@link SpPermission#READ_TENANT_CONFIGURATION} or
* {@link #IS_SYSTEM_CODE}.
*/
public static final String HAS_AUTH_TENANT_CONFIGURATION_READ = HAS_AUTH_PREFIX + READ_TENANT_CONFIGURATION
+ HAS_AUTH_SUFFIX + HAS_AUTH_OR + IS_SYSTEM_CODE;
/**
* Spring security eval hasAuthority expression to check if spring
* context contains {@link SpPermission#TENANT_CONFIGURATION} or
@@ -414,14 +435,10 @@ public final class SpPermission {
/**
* Spring security eval hasAuthority expression to check if spring
* context contains {@link SpPermission#IS_CONTROLLER} or
* context contains {@link #IS_CONTROLLER} or
* {@link #HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET}.
*/
public static final String IS_CONTROLLER_OR_HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET =
IS_CONTROLLER + HAS_AUTH_OR + HAS_AUTH_READ_REPOSITORY_AND_UPDATE_TARGET;
private SpringEvalExpressions() {
// utility class
}
}
}
}

View File

@@ -48,6 +48,10 @@ public final class SpRole {
ROLLOUT_ADMIN + IMPLIES + SpPermission.HANDLE_ROLLOUT + LINE_BREAK +
ROLLOUT_ADMIN + IMPLIES + SpPermission.APPROVE_ROLLOUT + LINE_BREAK;
public static final String TENANT_CONFIGURATION_HIERARCHY =
SpPermission.TENANT_CONFIGURATION + IMPLIES + SpPermission.READ_TENANT_CONFIGURATION + LINE_BREAK +
SpPermission.TENANT_CONFIGURATION + IMPLIES + SpPermission.READ_GATEWAY_SEC_TOKEN;
public static final String TENANT_ADMIN = "ROLE_TENANT_ADMIN";
public static final String TENANT_ADMIN_HIERARCHY =
TENANT_ADMIN + IMPLIES + TARGET_ADMIN + LINE_BREAK +
@@ -61,6 +65,8 @@ public final class SpRole {
public static String DEFAULT_ROLE_HIERARCHY =
TARGET_ADMIN_HIERARCHY +
REPOSITORY_ADMIN_HIERARCHY +
ROLLOUT_ADMIN_HIERARCHY + TENANT_ADMIN_HIERARCHY +
ROLLOUT_ADMIN_HIERARCHY +
TENANT_CONFIGURATION_HIERARCHY +
TENANT_ADMIN_HIERARCHY +
SYSTEM_ADMIN_HIERARCHY;
}

View File

@@ -34,7 +34,7 @@ public final class SpPermissionTest {
@Test
@Description("Verify the get permission function")
public void testGetPermissions() {
final int allPermission = 18;
final int allPermission = 20;
final Collection<String> allAuthorities = SpPermission.getAllAuthorities();
final List<GrantedAuthority> allAuthoritiesList = PermissionUtils.createAllAuthorityList();
assertThat(allAuthorities).hasSize(allPermission);