Extend access control management (#1493)

* Fix ACM related executions.

* Introduce access controller for actions. Resolve some todos and fix distribution set invalidation strategy.

* Do only check for access if returned values are access controlled.

* Fix review findings.

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.com>

---------

Signed-off-by: Michael Herdt <Michael.Herdt@bosch.com>
This commit is contained in:
Michael Herdt
2023-12-01 07:50:41 +01:00
committed by GitHub
parent a6fa75697f
commit 960ab6872d
16 changed files with 332 additions and 258 deletions

View File

@@ -22,6 +22,9 @@ import org.springframework.security.access.prepost.PreAuthorize;
public interface RolloutExecutor {
/**
* This execution should only be triggered by the system as a background job and
* not available via API.
*
* Process rollout based on its current {@link Rollout#getStatus()}.
*
* For {@link RolloutStatus#CREATING} that means creating the
@@ -29,9 +32,9 @@ public interface RolloutExecutor {
* {@link RolloutStatus#READY}.
*
* For {@link RolloutStatus#READY} that means switching to
* {@link RolloutStatus#STARTING} if the {@link Rollout#getStartAt()} is set
* and time of calling this method is beyond this point in time. This auto
* start mechanism is optional. Call {@link #start(Long)} otherwise.
* {@link RolloutStatus#STARTING} if the {@link Rollout#getStartAt()} is set and
* time of calling this method is beyond this point in time. This auto start
* mechanism is optional. Call {@link #start(Long)} otherwise.
*
* For {@link RolloutStatus#STARTING} that means starting the first
* {@link RolloutGroup}s in line and when finished switch to
@@ -45,8 +48,7 @@ public interface RolloutExecutor {
* rollout was already {@link RolloutStatus#RUNNING} which results in status
* change {@link RolloutStatus#DELETED} or hard delete from the persistence
* otherwise.
*
*/
@PreAuthorize(SpringEvalExpressions.IS_SYSTEM_CODE)
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_CREATE)
void execute(Rollout rollout);
}

View File

@@ -69,9 +69,11 @@ public interface TargetManagement {
/**
* Counts number of targets with the given distribution set assigned.
*
* @param distributionSetId to search for
* @param distributionSetId
* to search for
* @return number of found {@link Target}s.
* @throws EntityNotFoundException if distribution set with given ID does not exist
* @throws EntityNotFoundException
* if distribution set with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
@@ -81,8 +83,8 @@ public interface TargetManagement {
* Count {@link Target}s for all the given filter parameters.
*
* @param filterParams
* the filters to apply; only filters are enabled that have
* non-null value; filters are AND-gated
* the filters to apply; only filters are enabled that have non-null
* value; filters are AND-gated
*
* @return the found number {@link Target}s
*
@@ -95,21 +97,25 @@ public interface TargetManagement {
/**
* Get the count of targets with the given distribution set id.
*
* @param distributionSetId to search for
* @param distributionSetId
* to search for
* @return number of found {@link Target}s.
* @throws EntityNotFoundException if distribution set with given ID does not exist
* @throws EntityNotFoundException
* if distribution set with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
long countByInstalledDistributionSet(long distributionSetId);
/**
* Checks if there is already a {@link Target} that has the given
* distribution set Id assigned or installed.
* Checks if there is already a {@link Target} that has the given distribution
* set Id assigned or installed.
*
* @param distributionSetId to search for
* @param distributionSetId
* to search for
* @return <code>true</code> if a {@link Target} exists.
* @throws EntityNotFoundException if distribution set with given ID does not exist
* @throws EntityNotFoundException
* if distribution set with given ID does not exist
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET + SpringEvalExpressions.HAS_AUTH_OR
+ SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY)
@@ -126,23 +132,47 @@ public interface TargetManagement {
long countByRsql(@NotEmpty String rsqlParam);
/**
* Count all targets for given {@link TargetFilterQuery} and that are
* compatible with the passed {@link DistributionSetType}.
* Count {@link TargetFilterQuery}s for given target filter query with UPDATE permission.
*
* @param rsqlParam
* filter definition in RSQL syntax
* @param distributionSetId
* @return the found number of {@link Target}s
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
long countByRsqlAndUpdatable(@NotEmpty String rsqlParam);
/**
* Count all targets for given {@link TargetFilterQuery} and that are compatible
* with the passed {@link DistributionSetType}.
*
* @param rsqlParam
* filter definition in RSQL syntax
* @param distributionSetIdTypeId
* ID of the {@link DistributionSetType} the targets need to be
* compatible with
* @return the found number of{@link Target}s
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
long countByRsqlAndCompatible(@NotEmpty String rsqlParam, @NotNull Long distributionSetId);
long countByRsqlAndCompatible(@NotEmpty String rsqlParam, @NotNull Long distributionSetIdTypeId);
/**
* Count all targets with failed actions for specific Rollout
* and that are compatible with the passed {@link DistributionSetType}
* and created after given timestamp
* Count all targets for given {@link TargetFilterQuery} and that are compatible
* with the passed {@link DistributionSetType} and UPDATE permission.
*
* @param rsqlParam
* filter definition in RSQL syntax
* @param distributionSetIdTypeId
* ID of the {@link DistributionSetType} the targets need to be
* compatible with
* @return the found number of{@link Target}s
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
long countByRsqlAndCompatibleAndUpdatable(@NotEmpty String rsqlParam, @NotNull Long distributionSetIdTypeId);
/**
* Count all targets with failed actions for specific Rollout and that are
* compatible with the passed {@link DistributionSetType} and created after
* given timestamp
*
* @param rolloutId
* rolloutId of the rollout to be retried.
@@ -185,18 +215,17 @@ public interface TargetManagement {
* @throws EntityAlreadyExistsException
* given target already exists.
* @throws ConstraintViolationException
* if fields are not filled as specified. Check
* {@link TargetCreate} for field constraints.
* if fields are not filled as specified. Check {@link TargetCreate}
* for field constraints.
*
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_TARGET)
Target create(@NotNull @Valid TargetCreate create);
/**
* creates multiple {@link Target}s. If the given {@link Target}s
* already exists in the DB an {@link EntityAlreadyExistsException} is
* thrown. {@link Target}s contain all objects of the parameter targets,
* including duplicates.
* creates multiple {@link Target}s. If the given {@link Target}s already exists
* in the DB an {@link EntityAlreadyExistsException} is thrown. {@link Target}s
* contain all objects of the parameter targets, including duplicates.
*
* @param creates
* to be created.
@@ -205,8 +234,8 @@ public interface TargetManagement {
* @throws EntityAlreadyExistsException
* of one of the given targets already exist.
* @throws ConstraintViolationException
* if fields are not filled as specified. Check
* {@link TargetCreate} for field constraints.
* if fields are not filled as specified. Check {@link TargetCreate}
* for field constraints.
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_CREATE_TARGET)
List<Target> create(@NotNull @Valid Collection<TargetCreate> creates);
@@ -253,12 +282,12 @@ public interface TargetManagement {
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
Slice<Target> findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(@NotNull Pageable pageRequest,
long distributionSetId, @NotNull String rsqlParam);
long distributionSetId, @NotNull String rsqlParam);
/**
* Counts all targets for all the given parameter {@link TargetFilterQuery}
* and that don't have the specified distribution set in their action
* history and are compatible with the passed {@link DistributionSetType}.
* Counts all targets for all the given parameter {@link TargetFilterQuery} and
* that don't have the specified distribution set in their action history and
* are compatible with the passed {@link DistributionSetType}.
*
* @param distributionSetId
* id of the {@link DistributionSet}
@@ -273,9 +302,9 @@ public interface TargetManagement {
long countByRsqlAndNonDSAndCompatibleAndUpdatable(long distributionSetId, @NotNull String rsqlParam);
/**
* Finds all targets for all the given parameter {@link TargetFilterQuery}
* and that are not assigned to one of the {@link RolloutGroup}s and are
* compatible with the passed {@link DistributionSetType}.
* Finds all targets for all the given parameter {@link TargetFilterQuery} and
* that are not assigned to one of the {@link RolloutGroup}s and are compatible
* with the passed {@link DistributionSetType}.
*
* @param pageRequest
* the pageRequest to enhance the query for paging and sorting
@@ -284,8 +313,8 @@ public interface TargetManagement {
* @param rsqlParam
* filter definition in RSQL syntax
* @param distributionSetType
* type of the {@link DistributionSet} the targets must be
* compatible withs
* type of the {@link DistributionSet} the targets must be compatible
* withs
* @return a page of the found {@link Target}s
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
@@ -294,9 +323,9 @@ public interface TargetManagement {
@NotNull DistributionSetType distributionSetType);
/**
* Finds all targets with failed actions for specific Rollout
* and that are not assigned to one of the retried {@link RolloutGroup}s and are
* compatible with the passed {@link DistributionSetType}.
* Finds all targets with failed actions for specific Rollout and that are not
* assigned to one of the retried {@link RolloutGroup}s and are compatible with
* the passed {@link DistributionSetType}.
*
* @param pageRequest
* the pageRequest to enhance the query for paging and sorting
@@ -308,12 +337,12 @@ public interface TargetManagement {
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_TARGET)
Slice<Target> findByFailedRolloutAndNotInRolloutGroups(@NotNull Pageable pageRequest,
@NotEmpty Collection<Long> groups, @NotNull String rolloutId);
@NotEmpty Collection<Long> groups, @NotNull String rolloutId);
/**
* Counts all targets for all the given parameter {@link TargetFilterQuery}
* and that are not assigned to one of the {@link RolloutGroup}s and are
* compatible with the passed {@link DistributionSetType}.
* Counts all targets for all the given parameter {@link TargetFilterQuery} and
* that are not assigned to one of the {@link RolloutGroup}s and are compatible
* with the passed {@link DistributionSetType}.
*
* @param groups
* the list of {@link RolloutGroup}s
@@ -329,9 +358,9 @@ public interface TargetManagement {
@NotNull String rsqlParam, @NotNull DistributionSetType distributionSetType);
/**
* Counts all targets with failed actions for specific Rollout
* and that are not assigned to one of the {@link RolloutGroup}s and are
* compatible with the passed {@link DistributionSetType}.
* Counts all targets with failed actions for specific Rollout and that are not
* assigned to one of the {@link RolloutGroup}s and are compatible with the
* passed {@link DistributionSetType}.
*
* @param groups
* the list of {@link RolloutGroup}s
@@ -343,8 +372,8 @@ public interface TargetManagement {
long countByFailedRolloutAndNotInRolloutGroups(@NotEmpty Collection<Long> groups, @NotNull String rolloutId);
/**
* Finds all targets of the provided {@link RolloutGroup} that have no
* Action for the RolloutGroup.
* Finds all targets of the provided {@link RolloutGroup} that have no Action
* for the RolloutGroup.
*
* @param pageRequest
* the pageRequest to enhance the query for paging and sorting
@@ -376,8 +405,8 @@ public interface TargetManagement {
Page<Target> findByAssignedDistributionSet(@NotNull Pageable pageReq, long distributionSetId);
/**
* Retrieves {@link Target}s by the assigned {@link DistributionSet}
* possible including additional filtering based on the given {@code spec}.
* Retrieves {@link Target}s by the assigned {@link DistributionSet} possible
* including additional filtering based on the given {@code spec}.
*
* @param pageReq
* page parameter
@@ -420,14 +449,14 @@ public interface TargetManagement {
Optional<Target> getByControllerID(@NotEmpty String controllerId);
/**
* Filter {@link Target}s for all the given parameters. If all parameters
* except pageable are null, all available {@link Target}s are returned.
* Filter {@link Target}s for all the given parameters. If all parameters except
* pageable are null, all available {@link Target}s are returned.
*
* @param pageable
* page parameters
* @param filterParams
* the filters to apply; only filters are enabled that have
* non-null value; filters are AND-gated
* the filters to apply; only filters are enabled that have non-null
* value; filters are AND-gated
*
* @return the found {@link Target}s
*
@@ -454,8 +483,8 @@ public interface TargetManagement {
Page<Target> findByInstalledDistributionSet(@NotNull Pageable pageReq, long distributionSetId);
/**
* retrieves {@link Target}s by the installed {@link DistributionSet}
* including additional filtering based on the given {@code spec}.
* retrieves {@link Target}s by the installed {@link DistributionSet} including
* additional filtering based on the given {@code spec}.
*
* @param pageReq
* page parameter
@@ -480,8 +509,7 @@ public interface TargetManagement {
@NotNull String rsqlParam);
/**
* Retrieves the {@link Target} which have a certain
* {@link TargetUpdateStatus}.
* Retrieves the {@link Target} which have a certain {@link TargetUpdateStatus}.
*
* @param pageable
* page parameter
@@ -629,9 +657,9 @@ public interface TargetManagement {
/**
* Initiates {@link TargetType} assignment to given {@link Target}s. If some
* targets in the list have the {@link TargetType} not yet assigned, they
* will get assigned. If all targets are already of that type, there will be
* no un-assignment.
* targets in the list have the {@link TargetType} not yet assigned, they will
* get assigned. If all targets are already of that type, there will be no
* un-assignment.
*
* @param controllerIds
* to set the type to
@@ -647,8 +675,8 @@ public interface TargetManagement {
TargetTypeAssignmentResult assignType(@NotEmpty Collection<String> controllerIds, @NotNull Long typeId);
/**
* Initiates {@link TargetType} un-assignment to given {@link Target}s. The
* type of the targets will be set to {@code null}
* Initiates {@link TargetType} un-assignment to given {@link Target}s. The type
* of the targets will be set to {@code null}
*
* @param controllerIds
* to remove the type from
@@ -710,8 +738,8 @@ public interface TargetManagement {
* @throws EntityNotFoundException
* if given target does not exist
* @throws ConstraintViolationException
* if fields are not filled as specified. Check
* {@link TargetUpdate} for field constraints.
* if fields are not filled as specified. Check {@link TargetUpdate}
* for field constraints.
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_TARGET)
Target update(@NotNull @Valid TargetUpdate update);
@@ -797,30 +825,29 @@ public interface TargetManagement {
boolean existsByControllerId(@NotEmpty String controllerId);
/**
* Verify if a target matches a specific target filter query, does not have
* a specific DS already assigned and is compatible with it.
* Verify if a target matches a specific target filter query, does not have a
* specific DS already assigned and is compatible with it.
*
* @param controllerId
* of the {@link org.eclipse.hawkbit.repository.model.Target} to
* check
* @param distributionSetId
* of the
* {@link org.eclipse.hawkbit.repository.model.DistributionSet}
* to consider
* {@link org.eclipse.hawkbit.repository.model.DistributionSet} to
* consider
* @param targetFilterQuery
* to execute
* @return true if it matches
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_READ_REPOSITORY_AND_READ_TARGET)
boolean isTargetMatchingQueryAndDSNotAssignedAndCompatible(@NotNull String controllerId, long distributionSetId,
@NotNull String targetFilterQuery);
boolean isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(@NotNull String controllerId,
long distributionSetId, @NotNull String targetFilterQuery);
/**
* Creates a list of target meta data entries.
*
* @param controllerId
* {@link Target} controller id the metadata has to be created
* for
* {@link Target} controller id the metadata has to be created for
* @param metadata
* the meta data entries to create or update
* @return the updated or created target metadata entries
@@ -829,12 +856,12 @@ public interface TargetManagement {
* if given target does not exist
*
* @throws EntityAlreadyExistsException
* in case one of the metadata entry already exists for the
* specific key
* in case one of the metadata entry already exists for the specific
* key
*
* @throws AssignmentQuotaExceededException
* if the maximum number of {@link MetaData} entries is exceeded
* for the addressed {@link Target}
* if the maximum number of {@link MetaData} entries is exceeded for
* the addressed {@link Target}
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
List<TargetMetadata> createMetaData(@NotEmpty String controllerId, @NotEmpty Collection<MetaData> metadata);
@@ -928,15 +955,13 @@ public interface TargetManagement {
* Updates a target meta data value if corresponding entry exists.
*
* @param controllerId
* {@link Target} controller id of the metadata entry to be
* updated
* {@link Target} controller id of the metadata entry to be updated
* @param metadata
* meta data entry to be updated
* @return the updated meta data entry
*
* @throws EntityNotFoundException
* in case the metadata entry does not exist and cannot be
* updated
* in case the metadata entry does not exist and cannot be updated
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_UPDATE_REPOSITORY)
TargetMetadata updateMetadata(@NotEmpty String controllerId, @NotNull MetaData metadata);

View File

@@ -15,7 +15,10 @@ import java.util.Optional;
import javax.persistence.EntityManager;
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaTenantAwareBaseEntity;
import org.eclipse.hawkbit.repository.jpa.repository.BaseEntityRepository;
import org.eclipse.hawkbit.repository.jpa.repository.NoCountSliceRepository;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.springframework.data.domain.Page;
@@ -26,6 +29,7 @@ import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.CrudRepository;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
@@ -54,6 +58,9 @@ public final class JpaManagementHelper {
}
public static <J> Specification<J> combineWithAnd(final List<Specification<J>> specList) {
if (ObjectUtils.isEmpty(specList)) {
return Specification.where(null);
}
return specList.size() == 1 ? specList.get(0) : SpecificationsBuilder.combineWithAnd(specList);
}

View File

@@ -520,10 +520,10 @@ public class JpaRolloutExecutor implements RolloutExecutor {
final String baseFilter = RolloutHelper.getTargetFilterQuery(rollout);
final String groupTargetFilter;
if (StringUtils.isEmpty(group.getTargetFilterQuery())) {
groupTargetFilter = baseFilter;
} else {
if (StringUtils.hasText(group.getTargetFilterQuery())) {
groupTargetFilter = baseFilter + ";" + group.getTargetFilterQuery();
} else {
groupTargetFilter = baseFilter;
}
final List<Long> readyGroups = RolloutHelper.getGroupsByStatusIncludingGroup(rollout.getRolloutGroups(),

View File

@@ -101,6 +101,7 @@ import org.eclipse.hawkbit.repository.jpa.management.JpaTargetTagManagement;
import org.eclipse.hawkbit.repository.jpa.management.JpaTargetTypeManagement;
import org.eclipse.hawkbit.repository.jpa.management.JpaTenantConfigurationManagement;
import org.eclipse.hawkbit.repository.jpa.management.JpaTenantStatsManagement;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaArtifact;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
@@ -1038,13 +1039,12 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
final DistributionSetManagement distributionSetManagement, final RolloutManagement rolloutManagement,
final DeploymentManagement deploymentManagement,
final TargetFilterQueryManagement targetFilterQueryManagement, final ActionRepository actionRepository,
final PlatformTransactionManager txManager,
final RepositoryProperties repositoryProperties, final TenantAware tenantAware,
final LockRegistry lockRegistry) {
final PlatformTransactionManager txManager, final RepositoryProperties repositoryProperties,
final TenantAware tenantAware, final LockRegistry lockRegistry,
final SystemSecurityContext systemSecurityContext) {
return new JpaDistributionSetInvalidationManagement(distributionSetManagement, rolloutManagement,
deploymentManagement, targetFilterQueryManagement, actionRepository,
txManager, repositoryProperties, tenantAware,
lockRegistry);
deploymentManagement, targetFilterQueryManagement, actionRepository, txManager, repositoryProperties,
tenantAware, lockRegistry, systemSecurityContext);
}
/**
@@ -1080,10 +1080,12 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
@Autowired(required = false) final AccessController<JpaDistributionSetType> distributionSetTypeAccessController,
@Autowired(required = false) final AccessController<JpaDistributionSet> distributionSetAccessController,
@Autowired(required = false) final AccessController<JpaTargetType> targetTypeAccessControlManager,
@Autowired(required = false) final AccessController<JpaTarget> targetAccessControlManager) {
@Autowired(required = false) final AccessController<JpaTarget> targetAccessControlManager,
@Autowired(required = false) final AccessController<JpaAction> actionAccessController) {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(@NonNull final Object bean, @NonNull final String beanName) throws BeansException {
public Object postProcessAfterInitialization(@NonNull final Object bean, @NonNull final String beanName)
throws BeansException {
if (bean instanceof LocalArtifactRepository repo) {
return repo.withACM(artifactAccessController);
} else if (bean instanceof SoftwareModuleTypeRepository repo) {
@@ -1098,6 +1100,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
return repo.withACM(targetTypeAccessControlManager);
} else if (bean instanceof TargetRepository repo) {
return repo.withACM(targetAccessControlManager);
} else if (bean instanceof ActionRepository repo) {
return repo.withACM(actionAccessController);
}
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
}

View File

@@ -119,7 +119,7 @@ public class AutoAssignChecker extends AbstractAutoAssignExecutor {
LOGGER.debug("Auto assign check call for tenant {} and target filter query id {} for device {} started",
getContextAware().getCurrentTenant(), targetFilterQuery.getId(), controllerId);
try {
final boolean controllerIdMatches = targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible(
final boolean controllerIdMatches = targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(
controllerId, targetFilterQuery.getAutoAssignDistributionSet().getId(),
targetFilterQuery.getQuery());

View File

@@ -308,7 +308,6 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
throwExceptionIfTargetDoesNotExist(controllerId);
throwExceptionIfSoftwareModuleDoesNotExist(moduleId);
// TODO AC - REVIEW
// it used to perform 3-table join query
// @Query("Select a from JpaAction a join a.distributionSet ds join ds.modules modul where a.target.controllerId = :target and modul.id = :module order by a.id desc")
// final List<Action> actions = actionRepository.findActionByTargetAndSoftwareModule(controllerId, moduleId);

View File

@@ -158,14 +158,14 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
private final RetryTemplate retryTemplate;
public JpaDeploymentManagement(final EntityManager entityManager, final ActionRepository actionRepository,
final DistributionSetManagement distributionSetManagement,
final DistributionSetRepository distributionSetRepository, final TargetRepository targetRepository,
final ActionStatusRepository actionStatusRepository, final AuditorAware<String> auditorProvider,
final EventPublisherHolder eventPublisherHolder, final AfterTransactionCommitExecutor afterCommit,
final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager,
final TenantConfigurationManagement tenantConfigurationManagement, final QuotaManagement quotaManagement,
final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware, final Database database,
final RepositoryProperties repositoryProperties) {
final DistributionSetManagement distributionSetManagement,
final DistributionSetRepository distributionSetRepository, final TargetRepository targetRepository,
final ActionStatusRepository actionStatusRepository, final AuditorAware<String> auditorProvider,
final EventPublisherHolder eventPublisherHolder, final AfterTransactionCommitExecutor afterCommit,
final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager,
final TenantConfigurationManagement tenantConfigurationManagement, final QuotaManagement quotaManagement,
final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware, final Database database,
final RepositoryProperties repositoryProperties) {
super(actionRepository, actionStatusRepository, quotaManagement, repositoryProperties);
this.entityManager = entityManager;
this.distributionSetRepository = distributionSetRepository;
@@ -240,10 +240,10 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
deploymentRequests = deploymentRequests.stream().distinct().toList();
checkForMultiAssignment(deploymentRequests);
checkQuotaForAssignment(deploymentRequests);
// validates READ access to deployment sets, throws exception if deployment set is not accessible
// validates READ access to deployment sets, throws exception if deployment set
// is not accessible
checkForTargetTypeCompatibility(deploymentRequests);
// filters only targets that are updatable
// TODO - should assignments that contain non-existing/allowed devices be allowed anyway?
return filterByTargetUpdatable(deploymentRequests);
}
@@ -313,16 +313,12 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
}
private List<DeploymentRequest> filterByTargetUpdatable(final List<DeploymentRequest> deploymentRequests) {
final List<String> controllerIds =
deploymentRequests.stream()
.map(DeploymentRequest::getControllerId)
.distinct()
.toList();
final List<String> controllerIds = deploymentRequests.stream().map(DeploymentRequest::getControllerId)
.distinct().toList();
final List<String> found = targetRepository.findAll(
AccessController.Operation.UPDATE,
TargetSpecifications.hasControllerIdIn(controllerIds)
).stream().map(JpaTarget::getControllerId).toList();
final List<String> found = targetRepository
.findAll(AccessController.Operation.UPDATE, TargetSpecifications.hasControllerIdIn(controllerIds))
.stream().map(JpaTarget::getControllerId).toList();
if (found.size() != controllerIds.size()) {
return deploymentRequests.stream()
.filter(deploymentRequest -> found.contains(deploymentRequest.getControllerId())).toList();
@@ -384,8 +380,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
final List<String> existingTargetIds = Lists.partition(providedTargetIds, Constants.MAX_ENTRIES_IN_STATEMENT)
.stream()
.map(ids -> targetRepository.findAll(
AccessController.Operation.UPDATE, TargetSpecifications.hasControllerIdIn(ids)))
.map(ids -> targetRepository.findAll(AccessController.Operation.UPDATE,
TargetSpecifications.hasControllerIdIn(ids)))
.flatMap(List::stream).map(JpaTarget::getControllerId).toList();
final List<JpaTarget> targetEntities = assignmentStrategy.findTargetsForAssignment(existingTargetIds,
@@ -490,9 +486,10 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
public void cancelInactiveScheduledActionsForTargets(final List<Long> targetIds) {
if (!isMultiAssignmentsEnabled()) {
targetRepository.getAccessController().ifPresent(v -> {
if (targetRepository.count(AccessController.Operation.UPDATE, TargetSpecifications.hasIdIn(targetIds)) != targetIds.size()) {
throw new EntityNotFoundException(Target.class, targetIds);
}
if (targetRepository.count(AccessController.Operation.UPDATE,
TargetSpecifications.hasIdIn(targetIds)) != targetIds.size()) {
throw new EntityNotFoundException(Target.class, targetIds);
}
});
actionRepository.switchStatus(Status.CANCELED, targetIds, false, Status.SCHEDULED);
} else {
@@ -776,24 +773,21 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
@Override
public Optional<Action> findAction(final long actionId) {
return actionRepository
.findById(actionId)
return actionRepository.findById(actionId)
.filter(action -> targetRepository.exists(TargetSpecifications.hasId(action.getTarget().getId())))
.map(JpaAction.class::cast);
}
@Override
public Optional<Action> findActionWithDetails(final long actionId) {
return actionRepository
.findWithDetailsById(actionId)
return actionRepository.findWithDetailsById(actionId)
.filter(action -> targetRepository.exists(TargetSpecifications.hasId(action.getTarget().getId())));
}
@Override
public Slice<Action> findActionsByTarget(final String controllerId, final Pageable pageable) {
assertTargetReadAllowed(controllerId);
return actionRepository
.findAll(ActionSpecifications.byTargetControllerId(controllerId), pageable)
return actionRepository.findAll(ActionSpecifications.byTargetControllerId(controllerId), pageable)
.map(Action.class::cast);
}
@@ -853,8 +847,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
@Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Action forceTargetAction(final long actionId) {
final JpaAction action = actionRepository.findById(actionId)
.map(this::assertTargetUpdateAllowed)
final JpaAction action = actionRepository.findById(actionId).map(this::assertTargetUpdateAllowed)
.orElseThrow(() -> new EntityNotFoundException(Action.class, actionId));
if (!action.isForcedOrTimeForced()) {
@@ -878,7 +871,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
return actionStatusRepository.countByActionId(actionId);
}
// action is already got and there are checked read permissions - do not check permissions
// action is already got and there are checked read permissions - do not check
// permissions
// and UI which is to be removed
@Override
public Page<String> findMessagesByActionStatusId(final Pageable pageable, final long actionStatusId) {
@@ -921,15 +915,11 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
return JpaManagementHelper.countBySpec(actionRepository, specList);
}
// TODO - return via Mgmt API all actions (event for targets the use has no access - check if should and could
// be limited
@Override
public Slice<Action> findActionsAll(final Pageable pageable) {
return JpaManagementHelper.findAllWithoutCountBySpec(actionRepository, pageable, null);
}
// TODO - return via Mgmt API all actions (event for targets the use has no access - check if should and could
// be limited
@Override
public Slice<Action> findActions(final String rsqlParam, final Pageable pageable) {
final List<Specification<JpaAction>> specList = List.of(
@@ -983,8 +973,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
public boolean hasPendingCancellations(final Long targetId) {
// target access checked in assertTargetReadAllowed
assertTargetReadAllowed(targetId);
return actionRepository.exists(
ActionSpecifications.byTargetIdAndIsActiveAndStatus(targetId, Action.Status.CANCELING));
return actionRepository
.exists(ActionSpecifications.byTargetIdAndIsActiveAndStatus(targetId, Action.Status.CANCELING));
}
private static String getQueryForDeleteActionsByStatusAndLastModifiedBeforeString(final Database database) {
@@ -1039,29 +1029,33 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
@Override
@Transactional
public void cancelActionsForDistributionSet(
final CancelationType cancelationType, final DistributionSet distributionSet) {
actionRepository
.findAll(ActionSpecifications.byDistributionSetIdAndActiveAndStatusIsNot(distributionSet.getId(), Status.CANCELING))
public void cancelActionsForDistributionSet(final CancelationType cancelationType,
final DistributionSet distributionSet) {
actionRepository.findAll(ActionSpecifications
.byDistributionSetIdAndActiveAndStatusIsNot(distributionSet.getId(), Status.CANCELING))
.forEach(action -> {
try {
assertTargetUpdateAllowed(action);
cancelAction(action.getId());
LOG.debug("Action {} canceled", action.getId());
} catch (final InsufficientPermissionException e) {
// no access - skip it
LOG.trace("Could not cancel action {} due to insufficient permissions.", action.getId(), e);
} catch (final EntityNotFoundException e) {
LOG.trace("Could not cancel action {} due to entity not found exception.", action.getId(), e);
}
});
if (cancelationType == CancelationType.FORCE) {
actionRepository
.findAll(ActionSpecifications.byDistributionSetIdAndActive(distributionSet.getId()))
actionRepository.findAll(ActionSpecifications.byDistributionSetIdAndActive(distributionSet.getId()))
.forEach(action -> {
try {
assertTargetUpdateAllowed(action);
forceQuitAction(action.getId());
LOG.debug("Action {} force canceled", action.getId());
} catch (final InsufficientPermissionException e) {
// no access - skip it
LOG.trace("Could not cancel action {} due to insufficient permissions.", action.getId(), e);
} catch (final EntityNotFoundException e) {
LOG.trace("Could not cancel action {} due to entity not found exception.", action.getId(),
e);
}
});
}
@@ -1080,9 +1074,12 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
}
private JpaAction assertTargetUpdateAllowed(final JpaAction action) {
if (!targetRepository.exists(TargetSpecifications.hasId(action.getTarget().getId()))) {
targetRepository.findOne(TargetSpecifications.hasId(action.getTarget().getId())).ifPresentOrElse(target -> {
targetRepository.getAccessController()
.ifPresent(acm -> acm.assertOperationAllowed(AccessController.Operation.UPDATE, target));
}, () -> {
throw new EntityNotFoundException(Action.class, action);
}
});
return action;
}

View File

@@ -27,6 +27,7 @@ import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidationCount;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -50,13 +51,14 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet
private final RepositoryProperties repositoryProperties;
private final TenantAware tenantAware;
private final LockRegistry lockRegistry;
private final SystemSecurityContext systemSecurityContext;
public JpaDistributionSetInvalidationManagement(final DistributionSetManagement distributionSetManagement,
final RolloutManagement rolloutManagement, final DeploymentManagement deploymentManagement,
final TargetFilterQueryManagement targetFilterQueryManagement, final ActionRepository actionRepository,
final PlatformTransactionManager txManager,
final RepositoryProperties repositoryProperties, final TenantAware tenantAware,
final LockRegistry lockRegistry) {
final PlatformTransactionManager txManager, final RepositoryProperties repositoryProperties,
final TenantAware tenantAware, final LockRegistry lockRegistry,
final SystemSecurityContext systemSecurityContext) {
this.distributionSetManagement = distributionSetManagement;
this.rolloutManagement = rolloutManagement;
this.deploymentManagement = deploymentManagement;
@@ -66,13 +68,13 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet
this.repositoryProperties = repositoryProperties;
this.tenantAware = tenantAware;
this.lockRegistry = lockRegistry;
this.systemSecurityContext = systemSecurityContext;
}
@Override
public void invalidateDistributionSet(final DistributionSetInvalidation distributionSetInvalidation) {
LOG.debug("Invalidate distribution sets {}", distributionSetInvalidation.getDistributionSetIds());
final String tenant = tenantAware.getCurrentTenant();
if (shouldRolloutsBeCanceled(distributionSetInvalidation.getCancelationType(),
distributionSetInvalidation.isCancelRollouts())) {
final String handlerId = JpaRolloutManagement.createRolloutLockKey(tenant);
@@ -95,6 +97,7 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet
// no lock is needed as no rollout will be stopped
invalidateDistributionSetsInTransaction(distributionSetInvalidation, tenant);
}
}
private void invalidateDistributionSetsInTransaction(final DistributionSetInvalidation distributionSetInvalidation,
@@ -110,17 +113,25 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet
final boolean cancelRollouts) {
final DistributionSet set = distributionSetManagement.getValidAndComplete(setId);
distributionSetManagement.invalidate(set);
LOG.debug("Distribution set {} set to invalid", setId);
LOG.debug("Distribution set {} marked as invalid.", setId);
// rollout cancellation should only be permitted with UPDATE_ROLLOUT permission
if (shouldRolloutsBeCanceled(cancelationType, cancelRollouts)) {
LOG.debug("Cancel rollouts after ds invalidation. ID: {}", setId);
rolloutManagement.cancelRolloutsForDistributionSet(set);
}
if (cancelationType != CancelationType.NONE) {
deploymentManagement.cancelActionsForDistributionSet(cancelationType, set);
}
// Do run as system to ensure all actions (even invisible) are canceled due to invalidation.
systemSecurityContext.runAsSystem(() -> {
if (cancelationType != CancelationType.NONE) {
LOG.debug("Cancel actions after ds invalidation. ID: {}", setId);
deploymentManagement.cancelActionsForDistributionSet(cancelationType, set);
}
targetFilterQueryManagement.cancelAutoAssignmentForDistributionSet(setId);
LOG.debug("Cancel auto assignments after ds invalidation. ID: {}", setId);
targetFilterQueryManagement.cancelAutoAssignmentForDistributionSet(setId);
return null;
});
}
private static boolean shouldRolloutsBeCanceled(final CancelationType cancelationType,
@@ -131,13 +142,16 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet
@Override
public DistributionSetInvalidationCount countEntitiesForInvalidation(
final DistributionSetInvalidation distributionSetInvalidation) {
final Collection<Long> setIds = distributionSetInvalidation.getDistributionSetIds();
final long rolloutsCount = shouldRolloutsBeCanceled(distributionSetInvalidation.getCancelationType(),
distributionSetInvalidation.isCancelRollouts()) ? countRolloutsForInvalidation(setIds) : 0;
final long autoAssignmentsCount = countAutoAssignmentsForInvalidation(setIds);
final long actionsCount = countActionsForInvalidation(setIds, distributionSetInvalidation.getCancelationType());
return systemSecurityContext.runAsSystem(() -> {
final Collection<Long> setIds = distributionSetInvalidation.getDistributionSetIds();
final long rolloutsCount = shouldRolloutsBeCanceled(distributionSetInvalidation.getCancelationType(),
distributionSetInvalidation.isCancelRollouts()) ? countRolloutsForInvalidation(setIds) : 0;
final long autoAssignmentsCount = countAutoAssignmentsForInvalidation(setIds);
final long actionsCount = countActionsForInvalidation(setIds,
distributionSetInvalidation.getCancelationType());
return new DistributionSetInvalidationCount(rolloutsCount, autoAssignmentsCount, actionsCount);
return new DistributionSetInvalidationCount(rolloutsCount, autoAssignmentsCount, actionsCount);
});
}
private long countRolloutsForInvalidation(final Collection<Long> setIds) {
@@ -163,7 +177,9 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet
}
private long countActionsForSoftInvalidation(final Collection<Long> setIds) {
return setIds.stream().mapToLong(distributionSet -> actionRepository
.countByDistributionSetIdAndActiveIsTrueAndStatusIsNot(distributionSet, Status.CANCELING)).sum();
return setIds.stream()
.mapToLong(distributionSet -> actionRepository
.countByDistributionSetIdAndActiveIsTrueAndStatusIsNot(distributionSet, Status.CANCELING))
.sum();
}
}

View File

@@ -786,7 +786,7 @@ public class JpaDistributionSetManagement implements DistributionSetManagement {
public void invalidate(final DistributionSet distributionSet) {
final JpaDistributionSet jpaSet = (JpaDistributionSet) distributionSet;
jpaSet.invalidate();
distributionSetRepository.save(AccessController.Operation.DELETE, jpaSet);
distributionSetRepository.save(jpaSet);
}
private JpaDistributionSet getById(final long id) {

View File

@@ -96,6 +96,8 @@ import org.springframework.validation.annotation.Validated;
import com.google.common.collect.Lists;
import static org.eclipse.hawkbit.repository.jpa.JpaManagementHelper.combineWithAnd;
/**
* JPA implementation of {@link TargetManagement}.
*
@@ -130,7 +132,6 @@ public class JpaTargetManagement implements TargetManagement {
private final Database database;
public JpaTargetManagement(final EntityManager entityManager,
final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement,
final TargetRepository targetRepository, final TargetTypeRepository targetTypeRepository,
@@ -138,8 +139,8 @@ public class JpaTargetManagement implements TargetManagement {
final RolloutGroupRepository rolloutGroupRepository,
final TargetFilterQueryRepository targetFilterQueryRepository,
final TargetTagRepository targetTagRepository, final EventPublisherHolder eventPublisherHolder,
final TenantAware tenantAware,
final VirtualPropertyReplacer virtualPropertyReplacer, final Database database) {
final TenantAware tenantAware, final VirtualPropertyReplacer virtualPropertyReplacer,
final Database database) {
this.entityManager = entityManager;
this.distributionSetManagement = distributionSetManagement;
this.quotaManagement = quotaManagement;
@@ -161,8 +162,7 @@ public class JpaTargetManagement implements TargetManagement {
}
private JpaTarget getByControllerIdAndThrowIfNotFound(final String controllerId) {
return targetRepository
.findOne(TargetSpecifications.hasControllerId(controllerId))
return targetRepository.findOne(TargetSpecifications.hasControllerId(controllerId))
.orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId));
}
@@ -255,6 +255,9 @@ public class JpaTargetManagement implements TargetManagement {
final JpaTarget target = JpaManagementHelper.touch(entityManager, targetRepository,
getByControllerIdAndThrowIfNotFound(controllerId));
targetRepository.getAccessController()
.ifPresent(acm -> acm.assertOperationAllowed(AccessController.Operation.UPDATE, target));
targetMetadataRepository.deleteById(metadata.getId());
// target update event is set to ignore "lastModifiedAt" field, so it is
// not send automatically within the touch() method
@@ -312,17 +315,14 @@ public class JpaTargetManagement implements TargetManagement {
.orElseThrow(() -> new EntityNotFoundException(TargetFilterQuery.class, targetFilterQueryId));
return JpaManagementHelper.findAllWithoutCountBySpec(targetRepository, pageable,
List.of(
RSQLUtility.buildRsqlSpecification(targetFilterQuery.getQuery(), TargetFields.class,
virtualPropertyReplacer, database)));
List.of(RSQLUtility.buildRsqlSpecification(targetFilterQuery.getQuery(), TargetFields.class,
virtualPropertyReplacer, database)));
}
@Override
public Slice<Target> findByRsql(final Pageable pageable, final String targetFilterQuery) {
return JpaManagementHelper.findAllWithoutCountBySpec(targetRepository, pageable,
List.of(
RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class,
virtualPropertyReplacer, database)));
return JpaManagementHelper.findAllWithoutCountBySpec(targetRepository, pageable, List.of(RSQLUtility
.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer, database)));
}
@Override
@@ -481,7 +481,7 @@ public class JpaTargetManagement implements TargetManagement {
final TargetTag tag = targetTagRepository.findByNameEquals(tagName)
.orElseThrow(() -> new EntityNotFoundException(TargetTag.class, tagName));
final List<JpaTarget> allTargets = targetRepository
.findAll(AccessController.Operation.UPDATE, TargetSpecifications.byControllerIdWithTagsInJoin(controllerIds));
.findAll(TargetSpecifications.byControllerIdWithTagsInJoin(controllerIds));
if (allTargets.size() < controllerIds.size()) {
throw new EntityNotFoundException(Target.class, controllerIds,
allTargets.stream().map(Target::getControllerId).toList());
@@ -492,6 +492,7 @@ public class JpaTargetManagement implements TargetManagement {
// all are already assigned -> unassign
if (alreadyAssignedTargets.size() == allTargets.size()) {
alreadyAssignedTargets.forEach(target -> target.removeTag(tag));
return new TargetTagAssignmentResult(0, Collections.emptyList(),
Collections.unmodifiableList(alreadyAssignedTargets), tag);
@@ -501,9 +502,7 @@ public class JpaTargetManagement implements TargetManagement {
// some or none are assigned -> assign
allTargets.forEach(target -> target.addTag(tag));
final TargetTagAssignmentResult result = new TargetTagAssignmentResult(alreadyAssignedTargets.size(),
Collections
.unmodifiableList(allTargets.stream().map(targetRepository::save).collect(Collectors.toList())),
Collections.emptyList(), tag);
targetRepository.saveAll(allTargets), Collections.emptyList(), tag);
// no reason to persist the tag
entityManager.detach(tag);
@@ -528,7 +527,7 @@ public class JpaTargetManagement implements TargetManagement {
targetsWithoutSameType.forEach(target -> target.setTargetType(type));
final TargetTypeAssignmentResult result = new TargetTypeAssignmentResult(targetsWithSameType.size(),
targetsWithoutSameType.stream().map(targetRepository::save).toList(), Collections.emptyList(), type);
targetRepository.saveAll(targetsWithoutSameType), Collections.emptyList(), type);
// no reason to persist the type
entityManager.detach(type);
@@ -566,12 +565,15 @@ public class JpaTargetManagement implements TargetManagement {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public List<Target> assignTag(final Collection<String> controllerIds, final long tagId) {
final List<JpaTarget> allTargets = targetRepository
.findAll(AccessController.Operation.UPDATE, TargetSpecifications.byControllerIdWithTagsInJoin(controllerIds));
.findAll(TargetSpecifications.byControllerIdWithTagsInJoin(controllerIds));
if (allTargets.size() < controllerIds.size()) {
throw new EntityNotFoundException(Target.class, controllerIds,
allTargets.stream().map(Target::getControllerId).toList());
}
targetRepository.getAccessController()
.ifPresent(acm -> acm.assertOperationAllowed(AccessController.Operation.UPDATE, allTargets));
final JpaTargetTag tag = targetTagRepository.findById(tagId)
.orElseThrow(() -> new EntityNotFoundException(TargetTag.class, tagId));
@@ -619,6 +621,11 @@ public class JpaTargetManagement implements TargetManagement {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Target assignType(final String controllerId, final Long targetTypeId) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
targetRepository.getAccessController().ifPresent(acm -> {
acm.assertOperationAllowed(AccessController.Operation.UPDATE, target);
});
final JpaTargetType targetType = getTargetTypeByIdAndThrowIfNotFound(targetTypeId);
target.setTargetType(targetType);
return targetRepository.save(target);
@@ -626,7 +633,7 @@ public class JpaTargetManagement implements TargetManagement {
@Override
public Slice<Target> findByFilterOrderByLinkedDistributionSet(final Pageable pageable,
final long orderByDistributionSetId, final FilterParams filterParams) {
final long orderByDistributionSetId, final FilterParams filterParams) {
// remove default sort from pageable to not overwrite sorted spec
final OffsetBasedPageRequest unsortedPage = new OffsetBasedPageRequest(pageable.getOffset(),
pageable.getPageSize(), Sort.unsorted());
@@ -661,41 +668,42 @@ public class JpaTargetManagement implements TargetManagement {
@Override
public Slice<Target> findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(final Pageable pageRequest,
final long distributionSetId, final String targetFilterQuery) {
final long distributionSetId, final String targetFilterQuery) {
final DistributionSet jpaDistributionSet = distributionSetManagement.getOrElseThrowException(distributionSetId);
final Long distSetTypeId = jpaDistributionSet.getType().getId();
return targetRepository.findAllWithoutCount(
AccessController.Operation.UPDATE,
JpaManagementHelper.combineWithAnd(List.of(
RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer,
database),
TargetSpecifications.hasNotDistributionSetInActions(distributionSetId),
TargetSpecifications.isCompatibleWithDistributionSetType(distSetTypeId))),
pageRequest).map(Target.class::cast);
return targetRepository
.findAllWithoutCount(AccessController.Operation.UPDATE,
combineWithAnd(List.of(
RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class,
virtualPropertyReplacer, database),
TargetSpecifications.hasNotDistributionSetInActions(distributionSetId),
TargetSpecifications.isCompatibleWithDistributionSetType(distSetTypeId))),
pageRequest)
.map(Target.class::cast);
}
@Override
public Slice<Target> findByTargetFilterQueryAndNotInRolloutGroupsAndCompatibleAndUpdatable(
final Pageable pageRequest, final Collection<Long> groups, final String targetFilterQuery,
final DistributionSetType dsType) {
return targetRepository.findAllWithoutCount(
AccessController.Operation.UPDATE,
JpaManagementHelper.combineWithAnd(List.of(
RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer,
database),
TargetSpecifications.isNotInRolloutGroups(groups),
TargetSpecifications.isCompatibleWithDistributionSetType(dsType.getId()))),
pageRequest).map(Target.class::cast);
return targetRepository
.findAllWithoutCount(AccessController.Operation.UPDATE,
combineWithAnd(List.of(
RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class,
virtualPropertyReplacer, database),
TargetSpecifications.isNotInRolloutGroups(groups),
TargetSpecifications.isCompatibleWithDistributionSetType(dsType.getId()))),
pageRequest)
.map(Target.class::cast);
}
@Override
public Slice<Target> findByFailedRolloutAndNotInRolloutGroups(Pageable pageRequest, Collection<Long> groups,
String rolloutId) {
String rolloutId) {
final List<Specification<JpaTarget>> specList = Arrays.asList(
TargetSpecifications.failedActionsForRollout(rolloutId),
TargetSpecifications.isNotInRolloutGroups(groups)
);
TargetSpecifications.failedActionsForRollout(rolloutId),
TargetSpecifications.isNotInRolloutGroups(groups));
return JpaManagementHelper.findAllWithCountBySpec(targetRepository, pageRequest, specList);
}
@@ -712,34 +720,34 @@ public class JpaTargetManagement implements TargetManagement {
@Override
public long countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(final Collection<Long> groups,
final String targetFilterQuery, final DistributionSetType dsType) {
return targetRepository.count(AccessController.Operation.UPDATE, JpaManagementHelper.combineWithAnd(
List.of(
RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer,
database),
TargetSpecifications.isNotInRolloutGroups(groups),
TargetSpecifications.isCompatibleWithDistributionSetType(dsType.getId()))));
final String targetFilterQuery, final DistributionSetType dsType) {
return targetRepository.count(AccessController.Operation.UPDATE,
combineWithAnd(List.of(
RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class,
virtualPropertyReplacer, database),
TargetSpecifications.isNotInRolloutGroups(groups),
TargetSpecifications.isCompatibleWithDistributionSetType(dsType.getId()))));
}
@Override
public long countByFailedRolloutAndNotInRolloutGroups(Collection<Long> groups, String rolloutId) {
final List<Specification<JpaTarget>> specList = Arrays.asList(
TargetSpecifications.failedActionsForRollout(rolloutId),
TargetSpecifications.isNotInRolloutGroups(groups));
TargetSpecifications.failedActionsForRollout(rolloutId),
TargetSpecifications.isNotInRolloutGroups(groups));
return JpaManagementHelper.countBySpec(targetRepository, specList);
}
@Override
public long countByRsqlAndNonDSAndCompatibleAndUpdatable(final long distributionSetId,
final String targetFilterQuery) {
final String targetFilterQuery) {
final DistributionSet jpaDistributionSet = distributionSetManagement.getOrElseThrowException(distributionSetId);
final Long distSetTypeId = jpaDistributionSet.getType().getId();
return targetRepository.count(AccessController.Operation.UPDATE, JpaManagementHelper.combineWithAnd(
List.of(
RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer,
database),
return targetRepository.count(AccessController.Operation.UPDATE,
combineWithAnd(List.of(
RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class,
virtualPropertyReplacer, database),
TargetSpecifications.hasNotDistributionSetInActions(distributionSetId),
TargetSpecifications.isCompatibleWithDistributionSetType(distSetTypeId))));
}
@@ -798,27 +806,38 @@ public class JpaTargetManagement implements TargetManagement {
@Override
public long countByRsql(final String targetFilterQuery) {
return JpaManagementHelper.countBySpec(
targetRepository,
List.of(
RSQLUtility.buildRsqlSpecification(
targetFilterQuery, TargetFields.class, virtualPropertyReplacer, database)));
return JpaManagementHelper.countBySpec(targetRepository, List.of(RSQLUtility
.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer, database)));
}
@Override
public long countByRsqlAndCompatible(final String targetFilterQuery, final Long distributionSetId) {
final List<Specification<JpaTarget>> specList = List.of(
RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer,
database),
TargetSpecifications.isCompatibleWithDistributionSetType(distributionSetId));
public long countByRsqlAndUpdatable(String targetFilterQuery) {
final List<Specification<JpaTarget>> specList = List.of(RSQLUtility.buildRsqlSpecification(targetFilterQuery,
TargetFields.class, virtualPropertyReplacer, database));
return targetRepository.count(AccessController.Operation.UPDATE, combineWithAnd(specList));
}
@Override
public long countByRsqlAndCompatible(final String targetFilterQuery, final Long distributionSetIdTypeId) {
final List<Specification<JpaTarget>> specList = List.of(RSQLUtility.buildRsqlSpecification(targetFilterQuery,
TargetFields.class, virtualPropertyReplacer, database),
TargetSpecifications.isCompatibleWithDistributionSetType(distributionSetIdTypeId));
return JpaManagementHelper.countBySpec(targetRepository, specList);
}
@Override
public long countByRsqlAndCompatibleAndUpdatable(String targetFilterQuery, Long distributionSetIdTypeId) {
final List<Specification<JpaTarget>> specList = List.of(RSQLUtility.buildRsqlSpecification(targetFilterQuery,
TargetFields.class, virtualPropertyReplacer, database),
TargetSpecifications.isCompatibleWithDistributionSetType(distributionSetIdTypeId));
return targetRepository.count(AccessController.Operation.UPDATE, combineWithAnd(specList));
}
@Override
public long countByFailedInRollout(final String rolloutId, final Long dsTypeId) {
final List<Specification<JpaTarget>> specList = List.of(
TargetSpecifications.failedActionsForRollout(rolloutId));
final List<Specification<JpaTarget>> specList = List
.of(TargetSpecifications.failedActionsForRollout(rolloutId));
return JpaManagementHelper.countBySpec(targetRepository, specList);
}
@@ -856,6 +875,8 @@ public class JpaTargetManagement implements TargetManagement {
@Override
public void requestControllerAttributes(final String controllerId) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
targetRepository.getAccessController()
.ifPresent(acm -> acm.assertOperationAllowed(AccessController.Operation.UPDATE, target));
target.setRequestControllerAttributes(true);
eventPublisherHolder.getEventPublisher()
.publishEvent(new TargetAttributesRequestedEvent(tenantAware.getCurrentTenant(), target.getId(),
@@ -876,7 +897,7 @@ public class JpaTargetManagement implements TargetManagement {
}
@Override
public boolean isTargetMatchingQueryAndDSNotAssignedAndCompatible(final String controllerId,
public boolean isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(final String controllerId,
final long distributionSetId, final String targetFilterQuery) {
RSQLUtility.validateRsqlFor(targetFilterQuery, TargetFields.class);
final DistributionSet ds = distributionSetManagement.get(distributionSetId)
@@ -891,7 +912,7 @@ public class JpaTargetManagement implements TargetManagement {
final Specification<JpaTarget> combinedSpecification = Objects
.requireNonNull(SpecificationsBuilder.combineWithAnd(specList));
return targetRepository.exists(combinedSpecification);
return targetRepository.exists(AccessController.Operation.UPDATE, combinedSpecification);
}
@Override

View File

@@ -27,7 +27,6 @@ import javax.transaction.Transactional;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
@@ -70,13 +69,18 @@ public class BaseEntityRepositoryACM<T extends AbstractJpaTenantAwareBaseEntity>
if (method.getName().startsWith("find") || method.getName().startsWith("get")) {
final Object result = method.invoke(repository, args);
if (Iterable.class.isAssignableFrom(method.getReturnType())) {
for (final T e : ((Iterable<T>) result)) {
accessController.assertOperationAllowed(AccessController.Operation.READ, e);
for (final Object e : (Iterable<?>) result) {
if (repository.getDomainClass().isAssignableFrom(e.getClass())) {
accessController.assertOperationAllowed(AccessController.Operation.READ, (T) e);
}
}
} else if (Optional.class.isAssignableFrom(method.getReturnType())) {
return ((Optional<T>)result).filter(t -> isOperationAllowed(AccessController.Operation.READ, t, accessController));
} else if (Optional.class.isAssignableFrom(method.getReturnType()) && ((Optional<?>) result)
.filter(value -> repository.getDomainClass().isAssignableFrom(value.getClass()))
.isPresent()) {
return ((Optional<T>) result).filter(
t -> isOperationAllowed(AccessController.Operation.READ, t, accessController));
} else if (repository.getDomainClass().isAssignableFrom(method.getReturnType())) {
accessController.assertOperationAllowed(AccessController.Operation.READ, (T)result);
accessController.assertOperationAllowed(AccessController.Operation.READ, (T) result);
}
return result;
} else if ("toString".equals(method.getName()) && method.getParameterCount() == 0) {

View File

@@ -74,9 +74,9 @@ class AutoAssignCheckerTest {
when(targetFilterQueryManagement.findWithAutoAssignDS(any()))
.thenReturn(new SliceImpl<>(Arrays.asList(notMatching, matching)));
when(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target, ds, matching.getQuery()))
when(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(target, ds, matching.getQuery()))
.thenReturn(true);
when(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target, ds, notMatching.getQuery()))
when(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(target, ds, notMatching.getQuery()))
.thenReturn(false);
sut.checkSingleTarget(target);

View File

@@ -203,18 +203,18 @@ class DistributionSetInvalidationManagementTest extends AbstractJpaIntegrationTe
}
@Test
@Description("Verify that a user that has authority READ_REPOSITORY and UPDATE_REPOSITORY is not allowed to invalidate a distribution set")
@Description("Verify that a user that has authority READ_REPOSITORY and UPDATE_REPOSITORY is allowed to invalidate a distribution set")
@WithUser(authorities = { "READ_REPOSITORY", "UPDATE_REPOSITORY" })
void verifyInvalidateWithReadAndUpdateRepoAuthority() {
final InvalidationTestData invalidationTestData = systemSecurityContext
.runAsSystem(() -> createInvalidationTestData("verifyInvalidateWithUpdateRepoAuthority"));
assertThatExceptionOfType(InsufficientPermissionException.class)
.as("Insufficient permission exception expected")
.isThrownBy(() -> distributionSetInvalidationManagement
.invalidateDistributionSet(new DistributionSetInvalidation(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()),
CancelationType.NONE, false)));
distributionSetInvalidationManagement.invalidateDistributionSet(new DistributionSetInvalidation(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), CancelationType.NONE,
false));
assertThat(
distributionSetRepository.findById(invalidationTestData.getDistributionSet().getId()).get().isValid())
.isFalse();
}
@Test

View File

@@ -1267,7 +1267,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
final DistributionSet ds = testdataFactory.createDistributionSet();
final String filter = "metadata.key1==target1-value1";
assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target.getControllerId(),
assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(target.getControllerId(),
ds.getId(), filter)).isTrue();
}
@@ -1278,7 +1278,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
final DistributionSet ds = testdataFactory.createDistributionSet();
final String filter = "metadata.key==not_existing";
assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target.getControllerId(),
assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(target.getControllerId(),
ds.getId(), filter)).isFalse();
}
@@ -1292,7 +1292,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
assignDistributionSet(ds2, target);
final String filter = "name==*";
assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target.getControllerId(),
assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(target.getControllerId(),
ds1.getId(), filter)).isFalse();
}
@@ -1303,7 +1303,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
final Target target = testdataFactory.createTarget("target", "target", type.getId());
final DistributionSet ds = testdataFactory.createDistributionSet();
assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target.getControllerId(),
assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(target.getControllerId(),
ds.getId(), "name==*")).isFalse();
}
@@ -1314,9 +1314,9 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
final Long ds = testdataFactory.createDistributionSet().getId();
assertThatExceptionOfType(RSQLParameterSyntaxException.class).isThrownBy(() -> targetManagement
.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target, ds, "invalid_syntax"));
.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(target, ds, "invalid_syntax"));
assertThatExceptionOfType(RSQLParameterUnsupportedFieldException.class).isThrownBy(() -> targetManagement
.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target, ds, "invalid_field==1"));
.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(target, ds, "invalid_field==1"));
}
@Test
@@ -1324,7 +1324,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
void matchesFilterTargetNotExists() {
final DistributionSet ds = testdataFactory.createDistributionSet();
assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible("notExisting", ds.getId(),
assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable("notExisting", ds.getId(),
"name==*")).isFalse();
}
@@ -1334,7 +1334,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
final String target = testdataFactory.createTarget().getControllerId();
assertThatExceptionOfType(EntityNotFoundException.class).isThrownBy(
() -> targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target, 123, "name==*"));
() -> targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(target, 123, "name==*"));
}
private void validateFoundTargetsByRsql(final String rsqlFilter, final String... controllerIds) {

View File

@@ -134,14 +134,13 @@ public class AddRolloutWindowLayout extends AbstractRolloutWindowLayout {
}
private Long getTotalTargets(final String filterQuery, final Long distSetTypeId) {
// TODO AC - Check for updatable targets only
if (StringUtils.isEmpty(filterQuery)) {
return null;
}
if (distSetTypeId == null) {
return targetManagement.countByRsql(filterQuery);
return targetManagement.countByRsqlAndUpdatable(filterQuery);
}
return targetManagement.countByRsqlAndCompatible(filterQuery, distSetTypeId);
return targetManagement.countByRsqlAndCompatibleAndUpdatable(filterQuery, distSetTypeId);
}
private boolean isSimpleGroupsTabSelected() {