Finalize and polish fine-grained permission (#2660)

* Remove _REPOSITORY_ permissions -> replaced with _SOFTWARE_MODULE_, _SOFTWARE_MODULE_TYPE_, _DISTRIBUTION_SET_, _DISTRIBUTION_SET_TYPE_ permissions
* Still kept _ROLE_REPOSITORY_ADMIN_ role granting all repository fine-graned permissions
* Added dedicated _TARGET_TYPE_ permission set - the _TARGET_ permissions just grant _READ_TARGET_TYPE_ (analogically _SOFTWARE_MODULE_ permissions grant _READ_SOFTWARE_MODULE_TYPE_ and _DISTRIBUTION_SET_ grants _READ_DISTRIBUTON_SET_TYPE_
* Hierarcy is not configurable - could be completely replaced by setting spring application property org.eclipse.hawkbit.hierarchy or could be extended by adding rules using org.eclipse.hawkbit.hierarchy.ext

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-09-09 15:42:11 +03:00
committed by GitHub
parent f2e6344775
commit ae3a004da0
16 changed files with 182 additions and 219 deletions

View File

@@ -17,7 +17,7 @@ import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import jakarta.validation.ValidationException;
@@ -88,7 +88,6 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
private final DeploymentManagement deployManagement;
private final SystemManagement systemManagement;
private final MgmtDistributionSetMapper mgmtDistributionSetMapper;
private final SystemSecurityContext systemSecurityContext;
private final TenantConfigHelper tenantConfigHelper;
@SuppressWarnings("java:S107")
@@ -112,7 +111,6 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
this.tenantConfigHelper = TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement);
this.mgmtDistributionSetMapper = mgmtDistributionSetMapper;
this.systemManagement = systemManagement;
this.systemSecurityContext = systemSecurityContext;
}
@Override
@@ -144,23 +142,27 @@ public class MgmtDistributionSetResource implements MgmtDistributionSetRestApi {
public ResponseEntity<List<MgmtDistributionSet>> createDistributionSets(final List<MgmtDistributionSetRequestBodyPost> sets) {
log.debug("creating {} distribution sets", sets.size());
// set default Ds type if ds type is null
final String defaultDsKey = systemSecurityContext.runAsSystem(systemManagement.getTenantMetadata().getDefaultDsType()::getKey);
final String defaultDsKey = systemManagement.getTenantMetadata().getDefaultDsType().getKey();
sets.stream().filter(ds -> ds.getType() == null).forEach(ds -> ds.setType(defaultDsKey));
//check if there is already deleted DS Type
for (final MgmtDistributionSetRequestBodyPost ds : sets) {
final Optional<? extends DistributionSetType> opt = distributionSetTypeManagement.findByKey(ds.getType());
opt.ifPresent(dsType -> {
if (dsType.isDeleted()) {
final String text = "Cannot create Distribution Set from type with key {0}. Distribution Set Type already deleted!";
final String message = MessageFormat.format(text, dsType.getKey());
throw new ValidationException(message);
}
});
}
// check if target ds types exist and are not deleted, also caches them
final Map<String, DistributionSetType> dsTypeKeyToDsType = sets.stream()
.map(MgmtDistributionSetRequestBodyPost::getType)
.distinct()
.collect(Collectors.toMap(
Function.identity(),
dsTypeKey ->
distributionSetTypeManagement.findByKey(dsTypeKey).map(dsType -> {
if (dsType.isDeleted()) {
throw new ValidationException(MessageFormat.format(
"Cannot create Distribution Set from type with key {0}. Distribution Set Type already deleted!",
dsTypeKey));
}
return dsType;
}).orElseThrow(() -> new EntityNotFoundException(DistributionSetType.class, dsTypeKey))));
final Collection<? extends DistributionSet> createdDSets = distributionSetManagement
.create(mgmtDistributionSetMapper.fromRequest(sets));
final Collection<? extends DistributionSet> createdDSets =
distributionSetManagement.create(mgmtDistributionSetMapper.fromRequest(sets, defaultDsKey, dsTypeKeyToDsType));
log.debug("{} distribution sets created, return status {}", sets.size(), HttpStatus.CREATED);
return new ResponseEntity<>(MgmtDistributionSetMapper.toResponseDistributionSets(createdDSets), HttpStatus.CREATED);

View File

@@ -17,6 +17,7 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@@ -63,8 +64,38 @@ public class MgmtDistributionSetMapper {
this.systemManagement = systemManagement;
}
public List<DistributionSetManagement.Create> fromRequest(final Collection<MgmtDistributionSetRequestBodyPost> sets) {
return sets.stream().map(this::fromRequest).toList();
public List<DistributionSetManagement.Create> fromRequest(
final Collection<MgmtDistributionSetRequestBodyPost> sets,
final String defaultDsKey, final Map<String, DistributionSetType> dsTypeKeyToDsType) {
return sets.stream().<DistributionSetManagement.Create>map(dsRest -> {
final Set<Long> modules = new HashSet<>();
if (dsRest.getOs() != null) {
modules.add(dsRest.getOs().getId());
}
if (dsRest.getApplication() != null) {
modules.add(dsRest.getApplication().getId());
}
if (dsRest.getRuntime() != null) {
modules.add(dsRest.getRuntime().getId());
}
if (dsRest.getModules() != null) {
dsRest.getModules().forEach(module -> modules.add(module.getId()));
}
// distribution set type, if null by the REST call shall be replaced with the default tenant DS type
final String dsTypeKey = Objects.requireNonNull(dsRest.getType(), "Distribution set type must not be null");
final DistributionSetType dsType = dsTypeKeyToDsType.get(dsTypeKey);
if (dsType == null) {
// type should never null, cache is prefilled with all types
throw new EntityNotFoundException(DistributionSetType.class, defaultDsKey);
}
return DistributionSetManagement.Create.builder()
.type(dsType)
.name(dsRest.getName()).version(dsRest.getVersion())
.description(dsRest.getDescription())
.modules(findSoftwareModuleWithExceptionIfNotFound(modules))
.requiredMigrationStep(dsRest.getRequiredMigrationStep())
.build();
}).toList();
}
public static MgmtDistributionSet toResponse(final DistributionSet distributionSet) {
@@ -170,36 +201,6 @@ public class MgmtDistributionSetMapper {
return sets.stream().map(MgmtDistributionSetMapper::toResponse).toList();
}
private DistributionSetManagement.Create fromRequest(final MgmtDistributionSetRequestBodyPost dsRest) {
final Set<Long> modules = new HashSet<>();
if (dsRest.getOs() != null) {
modules.add(dsRest.getOs().getId());
}
if (dsRest.getApplication() != null) {
modules.add(dsRest.getApplication().getId());
}
if (dsRest.getRuntime() != null) {
modules.add(dsRest.getRuntime().getId());
}
if (dsRest.getModules() != null) {
dsRest.getModules().forEach(module -> modules.add(module.getId()));
}
return DistributionSetManagement.Create.builder()
.type(Optional.ofNullable(dsRest.getType())
// if the type is supplied the type MUST exist
.map(typeKey -> distributionSetTypeManagement
.findByKey(typeKey)
.orElseThrow(() -> new EntityNotFoundException(DistributionSetType.class, typeKey)))
.map(DistributionSetType.class::cast)
// if here, the type is not supplied, use the default type
.orElseGet(() -> systemManagement.getTenantMetadata().getDefaultDsType()))
.name(dsRest.getName()).version(dsRest.getVersion())
.description(dsRest.getDescription())
.modules(findSoftwareModuleWithExceptionIfNotFound(modules))
.requiredMigrationStep(dsRest.getRequiredMigrationStep())
.build();
}
private Set<? extends SoftwareModule> findSoftwareModuleWithExceptionIfNotFound(final Set<Long> softwareModuleIds) {
if (CollectionUtils.isEmpty(softwareModuleIds)) {
return Collections.emptySet();

View File

@@ -73,7 +73,6 @@ class MgmtTargetTypeResourceTest extends AbstractManagementApiIntegrationTest {
principal = "targetTypeTester", allSpPermissions = true,
removeFromAllPermission = {
SpPermission.CREATE_TARGET, SpPermission.READ_TARGET, SpPermission.UPDATE_TARGET, SpPermission.DELETE_TARGET,
SpPermission.CREATE_REPOSITORY, SpPermission.READ_REPOSITORY, SpPermission.UPDATE_REPOSITORY, SpPermission.DELETE_REPOSITORY,
SpPermission.READ_TARGET_TYPE })
void getTargetTypesWithoutPermission() throws Exception {
mvc.perform(get(TARGETTYPES_ENDPOINT).accept(MediaType.APPLICATION_JSON))