Fine grained repository permissions (#2562)

1. Introduce @PrreAuthorize check based on hasPermission - allowing custom processing (compared with non-modifiable hasAuthority/Role processing)
2. Dedicated permissions could be implemented on management api level. Check is made by plugged in PermissionEvaluator
3. Thus common XXX_REPOSITORY permissions could differ for extending services
4. Change create/update entity builder pattern - not via EntityFactory but via clean static lombok based builders (with fine fluent api).
5. Implement abstract repository management jpa class that handles the boilerplate code from extending classes in single place consistently -> AbsreactJpaRepositoryManagement
6. Register management api-s as **Sevice**-s instead of **Bean**-s in order to make easier maintainable and get away from heavy argument forwading
7. Simplify custom hawkbit repository registration + adding proxy to handle exception mapping at lower level - thus not depending on Aspects for converting exceptions
8. Implemented general purpose 'copy' utility (ObjectCopyUtil) that using getter/setter patterns is able to copy (e.g. Create/Update) objects to other objects (e.g. JPA entity objects)
This commit is contained in:
Avgustin Marinov
2025-07-28 14:57:33 +03:00
committed by GitHub
parent 8cdbe54cbe
commit 2b66449ff1
214 changed files with 3456 additions and 4416 deletions

View File

@@ -14,6 +14,7 @@ import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
import lombok.extern.slf4j.Slf4j;
import org.aopalliance.intercept.MethodInvocation;
import org.eclipse.hawkbit.im.authentication.Hierarchy;
import org.eclipse.hawkbit.tenancy.configuration.ControllerPollProperties;
@@ -26,6 +27,8 @@ 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.PermissionEvaluator;
import org.springframework.security.access.expression.DenyAllPermissionEvaluator;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations;
@@ -40,6 +43,7 @@ import org.springframework.util.function.SingletonSupplier;
/**
* Default configuration that is common to all repository implementations.
*/
@Slf4j
@Configuration
@EnableMethodSecurity(proxyTargetClass = true, securedEnabled = true)
@EnableConfigurationProperties({ RepositoryProperties.class, ControllerPollProperties.class, TenantConfigurationProperties.class })
@@ -52,10 +56,37 @@ public class RepositoryConfiguration {
return RoleHierarchyImpl.fromHierarchy(Hierarchy.DEFAULT);
}
@Bean
PermissionEvaluator permissionEvaluator(final RoleHierarchy roleHierarchy) {
return new DenyAllPermissionEvaluator() {
@Override
public boolean hasPermission(final Authentication authentication, final Object targetDomainObject, final Object permission) {
if (targetDomainObject instanceof MethodSecurityExpressionOperations root) {
final String neededPermission =
permission + "_" + (root.getThis() instanceof RepositoryManagement<?, ?, ?> repositoryManagement
? repositoryManagement.permissionGroup()
: "REPOSITORY"); // TODO - should not fall back here - all using parmissions should extend repository management interface
final boolean hasPermission = roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities()).stream()
.map(GrantedAuthority::getAuthority)
.anyMatch(authority -> authority.equals(neededPermission));
if (!hasPermission) {
log.debug(
"User {} does not have permission {} for target {}",
authentication.getName(), neededPermission, targetDomainObject);
}
return hasPermission;
}
return super.hasPermission(authentication, targetDomainObject, permission);
}
};
}
@Bean
@Primary
MethodSecurityExpressionHandler methodSecurityExpressionHandler(
final Optional<RoleHierarchy> roleHierarchy, final Optional<ApplicationContext> applicationContext) {
final RoleHierarchy roleHierarchy, final PermissionEvaluator permissionEvaluator,
final Optional<ApplicationContext> applicationContext) {
final DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler = new DefaultMethodSecurityExpressionHandler() {
@Override
@@ -69,7 +100,8 @@ public class RepositoryConfiguration {
return super.createSecurityExpressionRoot(new RawAuthoritiesAuthentication(authentication), mi);
}
};
roleHierarchy.ifPresent(methodSecurityExpressionHandler::setRoleHierarchy);
methodSecurityExpressionHandler.setRoleHierarchy(roleHierarchy);
methodSecurityExpressionHandler.setPermissionEvaluator(permissionEvaluator);
applicationContext.ifPresent(methodSecurityExpressionHandler::setApplicationContext);
return methodSecurityExpressionHandler;
}

View File

@@ -1,42 +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.builder;
import java.util.Collection;
import java.util.Optional;
/**
* Create and update builder DTO.
*
* @param <T> update or create builder interface
*/
public abstract class AbstractDistributionSetTypeUpdateCreate<T> extends AbstractTypeUpdateCreate<T> {
protected Collection<Long> mandatory;
protected Collection<Long> optional;
public T mandatory(final Collection<Long> mandatory) {
this.mandatory = mandatory;
return (T) this;
}
public T optional(final Collection<Long> optional) {
this.optional = optional;
return (T) this;
}
public Optional<Collection<Long>> getMandatory() {
return Optional.ofNullable(mandatory);
}
public Optional<Collection<Long>> getOptional() {
return Optional.ofNullable(optional);
}
}

View File

@@ -1,53 +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.builder;
import java.util.Collection;
import java.util.Optional;
import lombok.Getter;
import org.eclipse.hawkbit.repository.ValidString;
/**
* Create and update builder DTO.
*
* @param <T> update or create builder interface
*/
public abstract class AbstractDistributionSetUpdateCreate<T> extends AbstractNamedEntityBuilder<T> {
@ValidString
protected String version;
protected Boolean requiredMigrationStep;
@Getter
protected Collection<Long> modules;
public T modules(final Collection<Long> modules) {
this.modules = modules;
return (T) this;
}
public T requiredMigrationStep(final Boolean requiredMigrationStep) {
this.requiredMigrationStep = requiredMigrationStep;
return (T) this;
}
public Boolean isRequiredMigrationStep() {
return requiredMigrationStep;
}
public T version(final String version) {
this.version = strip(version);
return (T) this;
}
public Optional<String> getVersion() {
return Optional.ofNullable(version);
}
}

View File

@@ -1,29 +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.builder;
/**
* Create and update builder DTO.
*
* @param <T> update or create builder interface
*/
public abstract class AbstractSoftwareModuleTypeUpdateCreate<T> extends AbstractTypeUpdateCreate<T> {
protected int maxAssignments = 1;
public T maxAssignments(final int maxAssignments) {
this.maxAssignments = maxAssignments;
return (T) this;
}
public int getMaxAssignments() {
return maxAssignments;
}
}

View File

@@ -1,56 +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.builder;
import java.util.Optional;
import org.eclipse.hawkbit.repository.ValidString;
/**
* Create and update builder DTO.
*
* @param <T> update or create builder interface
*/
public abstract class AbstractSoftwareModuleUpdateCreate<T> extends AbstractNamedEntityBuilder<T> {
@ValidString
protected String version;
@ValidString
protected String vendor;
@ValidString
protected String type;
public T type(final String type) {
this.type = AbstractBaseEntityBuilder.strip(type);
return (T) this;
}
public Optional<String> getType() {
return Optional.ofNullable(type);
}
public T vendor(final String vendor) {
this.vendor = AbstractBaseEntityBuilder.strip(vendor);
return (T) this;
}
public Optional<String> getVendor() {
return Optional.ofNullable(vendor);
}
public T version(final String version) {
this.version = AbstractBaseEntityBuilder.strip(version);
return (T) this;
}
public Optional<String> getVersion() {
return Optional.ofNullable(version);
}
}

View File

@@ -1,21 +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.builder;
/**
* Update implementation.
*/
public class GenericDistributionSetTypeUpdate extends AbstractDistributionSetTypeUpdateCreate<DistributionSetTypeUpdate>
implements DistributionSetTypeUpdate {
public GenericDistributionSetTypeUpdate(final Long id) {
super.id = id;
}
}

View File

@@ -1,34 +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.builder;
import jakarta.annotation.Nullable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.experimental.Accessors;
/**
* Update implementation.
*/
@Data
@Accessors(fluent = true) // override locked()
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class GenericDistributionSetUpdate extends AbstractDistributionSetUpdateCreate<DistributionSetUpdate> implements DistributionSetUpdate {
@Nullable
protected Boolean locked;
public GenericDistributionSetUpdate(final Long id) {
super.id = id;
}
}

View File

@@ -1,21 +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.builder;
/**
* Update implementation.
*/
public class GenericSoftwareModuleTypeUpdate extends AbstractSoftwareModuleTypeUpdateCreate<SoftwareModuleTypeUpdate>
implements SoftwareModuleTypeUpdate {
public GenericSoftwareModuleTypeUpdate(final Long id) {
super.id = id;
}
}

View File

@@ -1,35 +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.builder;
import jakarta.annotation.Nullable;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.experimental.Accessors;
/**
* Update implementation.
*/
@Data
@Accessors(fluent = true) // override locked()
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class GenericSoftwareModuleUpdate extends AbstractSoftwareModuleUpdateCreate<SoftwareModuleUpdate>
implements SoftwareModuleUpdate {
@Nullable
protected Boolean locked;
public GenericSoftwareModuleUpdate(final Long id) {
super.id = id;
}
}