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 { 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()}. * Process rollout based on its current {@link Rollout#getStatus()}.
* *
* For {@link RolloutStatus#CREATING} that means creating the * For {@link RolloutStatus#CREATING} that means creating the
@@ -29,9 +32,9 @@ public interface RolloutExecutor {
* {@link RolloutStatus#READY}. * {@link RolloutStatus#READY}.
* *
* For {@link RolloutStatus#READY} that means switching to * For {@link RolloutStatus#READY} that means switching to
* {@link RolloutStatus#STARTING} if the {@link Rollout#getStartAt()} is set * {@link RolloutStatus#STARTING} if the {@link Rollout#getStartAt()} is set and
* and time of calling this method is beyond this point in time. This auto * time of calling this method is beyond this point in time. This auto start
* start mechanism is optional. Call {@link #start(Long)} otherwise. * mechanism is optional. Call {@link #start(Long)} otherwise.
* *
* For {@link RolloutStatus#STARTING} that means starting the first * For {@link RolloutStatus#STARTING} that means starting the first
* {@link RolloutGroup}s in line and when finished switch to * {@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 * rollout was already {@link RolloutStatus#RUNNING} which results in status
* change {@link RolloutStatus#DELETED} or hard delete from the persistence * change {@link RolloutStatus#DELETED} or hard delete from the persistence
* otherwise. * otherwise.
*
*/ */
@PreAuthorize(SpringEvalExpressions.IS_SYSTEM_CODE) @PreAuthorize(SpringEvalExpressions.HAS_AUTH_ROLLOUT_MANAGEMENT_CREATE)
void execute(Rollout rollout); void execute(Rollout rollout);
} }

View File

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

View File

@@ -15,7 +15,10 @@ import java.util.Optional;
import javax.persistence.EntityManager; 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.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.repository.NoCountSliceRepository;
import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder; import org.eclipse.hawkbit.repository.jpa.specifications.SpecificationsBuilder;
import org.springframework.data.domain.Page; 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.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
/** /**
@@ -54,6 +58,9 @@ public final class JpaManagementHelper {
} }
public static <J> Specification<J> combineWithAnd(final List<Specification<J>> specList) { 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); 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 baseFilter = RolloutHelper.getTargetFilterQuery(rollout);
final String groupTargetFilter; final String groupTargetFilter;
if (StringUtils.isEmpty(group.getTargetFilterQuery())) { if (StringUtils.hasText(group.getTargetFilterQuery())) {
groupTargetFilter = baseFilter;
} else {
groupTargetFilter = baseFilter + ";" + group.getTargetFilterQuery(); groupTargetFilter = baseFilter + ";" + group.getTargetFilterQuery();
} else {
groupTargetFilter = baseFilter;
} }
final List<Long> readyGroups = RolloutHelper.getGroupsByStatusIncludingGroup(rollout.getRolloutGroups(), 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.JpaTargetTypeManagement;
import org.eclipse.hawkbit.repository.jpa.management.JpaTenantConfigurationManagement; import org.eclipse.hawkbit.repository.jpa.management.JpaTenantConfigurationManagement;
import org.eclipse.hawkbit.repository.jpa.management.JpaTenantStatsManagement; 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.JpaArtifact;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType; import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
@@ -1038,13 +1039,12 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
final DistributionSetManagement distributionSetManagement, final RolloutManagement rolloutManagement, final DistributionSetManagement distributionSetManagement, final RolloutManagement rolloutManagement,
final DeploymentManagement deploymentManagement, final DeploymentManagement deploymentManagement,
final TargetFilterQueryManagement targetFilterQueryManagement, final ActionRepository actionRepository, final TargetFilterQueryManagement targetFilterQueryManagement, final ActionRepository actionRepository,
final PlatformTransactionManager txManager, final PlatformTransactionManager txManager, final RepositoryProperties repositoryProperties,
final RepositoryProperties repositoryProperties, final TenantAware tenantAware, final TenantAware tenantAware, final LockRegistry lockRegistry,
final LockRegistry lockRegistry) { final SystemSecurityContext systemSecurityContext) {
return new JpaDistributionSetInvalidationManagement(distributionSetManagement, rolloutManagement, return new JpaDistributionSetInvalidationManagement(distributionSetManagement, rolloutManagement,
deploymentManagement, targetFilterQueryManagement, actionRepository, deploymentManagement, targetFilterQueryManagement, actionRepository, txManager, repositoryProperties,
txManager, repositoryProperties, tenantAware, tenantAware, lockRegistry, systemSecurityContext);
lockRegistry);
} }
/** /**
@@ -1080,10 +1080,12 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
@Autowired(required = false) final AccessController<JpaDistributionSetType> distributionSetTypeAccessController, @Autowired(required = false) final AccessController<JpaDistributionSetType> distributionSetTypeAccessController,
@Autowired(required = false) final AccessController<JpaDistributionSet> distributionSetAccessController, @Autowired(required = false) final AccessController<JpaDistributionSet> distributionSetAccessController,
@Autowired(required = false) final AccessController<JpaTargetType> targetTypeAccessControlManager, @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() { return new BeanPostProcessor() {
@Override @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) { if (bean instanceof LocalArtifactRepository repo) {
return repo.withACM(artifactAccessController); return repo.withACM(artifactAccessController);
} else if (bean instanceof SoftwareModuleTypeRepository repo) { } else if (bean instanceof SoftwareModuleTypeRepository repo) {
@@ -1098,6 +1100,8 @@ public class RepositoryApplicationConfiguration extends JpaBaseConfiguration {
return repo.withACM(targetTypeAccessControlManager); return repo.withACM(targetTypeAccessControlManager);
} else if (bean instanceof TargetRepository repo) { } else if (bean instanceof TargetRepository repo) {
return repo.withACM(targetAccessControlManager); return repo.withACM(targetAccessControlManager);
} else if (bean instanceof ActionRepository repo) {
return repo.withACM(actionAccessController);
} }
return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName); 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", LOGGER.debug("Auto assign check call for tenant {} and target filter query id {} for device {} started",
getContextAware().getCurrentTenant(), targetFilterQuery.getId(), controllerId); getContextAware().getCurrentTenant(), targetFilterQuery.getId(), controllerId);
try { try {
final boolean controllerIdMatches = targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible( final boolean controllerIdMatches = targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(
controllerId, targetFilterQuery.getAutoAssignDistributionSet().getId(), controllerId, targetFilterQuery.getAutoAssignDistributionSet().getId(),
targetFilterQuery.getQuery()); targetFilterQuery.getQuery());

View File

@@ -308,7 +308,6 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
throwExceptionIfTargetDoesNotExist(controllerId); throwExceptionIfTargetDoesNotExist(controllerId);
throwExceptionIfSoftwareModuleDoesNotExist(moduleId); throwExceptionIfSoftwareModuleDoesNotExist(moduleId);
// TODO AC - REVIEW
// it used to perform 3-table join query // 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") // @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); // 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; private final RetryTemplate retryTemplate;
public JpaDeploymentManagement(final EntityManager entityManager, final ActionRepository actionRepository, public JpaDeploymentManagement(final EntityManager entityManager, final ActionRepository actionRepository,
final DistributionSetManagement distributionSetManagement, final DistributionSetManagement distributionSetManagement,
final DistributionSetRepository distributionSetRepository, final TargetRepository targetRepository, final DistributionSetRepository distributionSetRepository, final TargetRepository targetRepository,
final ActionStatusRepository actionStatusRepository, final AuditorAware<String> auditorProvider, final ActionStatusRepository actionStatusRepository, final AuditorAware<String> auditorProvider,
final EventPublisherHolder eventPublisherHolder, final AfterTransactionCommitExecutor afterCommit, final EventPublisherHolder eventPublisherHolder, final AfterTransactionCommitExecutor afterCommit,
final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager, final VirtualPropertyReplacer virtualPropertyReplacer, final PlatformTransactionManager txManager,
final TenantConfigurationManagement tenantConfigurationManagement, final QuotaManagement quotaManagement, final TenantConfigurationManagement tenantConfigurationManagement, final QuotaManagement quotaManagement,
final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware, final Database database, final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware, final Database database,
final RepositoryProperties repositoryProperties) { final RepositoryProperties repositoryProperties) {
super(actionRepository, actionStatusRepository, quotaManagement, repositoryProperties); super(actionRepository, actionStatusRepository, quotaManagement, repositoryProperties);
this.entityManager = entityManager; this.entityManager = entityManager;
this.distributionSetRepository = distributionSetRepository; this.distributionSetRepository = distributionSetRepository;
@@ -240,10 +240,10 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
deploymentRequests = deploymentRequests.stream().distinct().toList(); deploymentRequests = deploymentRequests.stream().distinct().toList();
checkForMultiAssignment(deploymentRequests); checkForMultiAssignment(deploymentRequests);
checkQuotaForAssignment(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); checkForTargetTypeCompatibility(deploymentRequests);
// filters only targets that are updatable // filters only targets that are updatable
// TODO - should assignments that contain non-existing/allowed devices be allowed anyway?
return filterByTargetUpdatable(deploymentRequests); return filterByTargetUpdatable(deploymentRequests);
} }
@@ -313,16 +313,12 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
} }
private List<DeploymentRequest> filterByTargetUpdatable(final List<DeploymentRequest> deploymentRequests) { private List<DeploymentRequest> filterByTargetUpdatable(final List<DeploymentRequest> deploymentRequests) {
final List<String> controllerIds = final List<String> controllerIds = deploymentRequests.stream().map(DeploymentRequest::getControllerId)
deploymentRequests.stream() .distinct().toList();
.map(DeploymentRequest::getControllerId)
.distinct()
.toList();
final List<String> found = targetRepository.findAll( final List<String> found = targetRepository
AccessController.Operation.UPDATE, .findAll(AccessController.Operation.UPDATE, TargetSpecifications.hasControllerIdIn(controllerIds))
TargetSpecifications.hasControllerIdIn(controllerIds) .stream().map(JpaTarget::getControllerId).toList();
).stream().map(JpaTarget::getControllerId).toList();
if (found.size() != controllerIds.size()) { if (found.size() != controllerIds.size()) {
return deploymentRequests.stream() return deploymentRequests.stream()
.filter(deploymentRequest -> found.contains(deploymentRequest.getControllerId())).toList(); .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) final List<String> existingTargetIds = Lists.partition(providedTargetIds, Constants.MAX_ENTRIES_IN_STATEMENT)
.stream() .stream()
.map(ids -> targetRepository.findAll( .map(ids -> targetRepository.findAll(AccessController.Operation.UPDATE,
AccessController.Operation.UPDATE, TargetSpecifications.hasControllerIdIn(ids))) TargetSpecifications.hasControllerIdIn(ids)))
.flatMap(List::stream).map(JpaTarget::getControllerId).toList(); .flatMap(List::stream).map(JpaTarget::getControllerId).toList();
final List<JpaTarget> targetEntities = assignmentStrategy.findTargetsForAssignment(existingTargetIds, 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) { public void cancelInactiveScheduledActionsForTargets(final List<Long> targetIds) {
if (!isMultiAssignmentsEnabled()) { if (!isMultiAssignmentsEnabled()) {
targetRepository.getAccessController().ifPresent(v -> { targetRepository.getAccessController().ifPresent(v -> {
if (targetRepository.count(AccessController.Operation.UPDATE, TargetSpecifications.hasIdIn(targetIds)) != targetIds.size()) { if (targetRepository.count(AccessController.Operation.UPDATE,
throw new EntityNotFoundException(Target.class, targetIds); TargetSpecifications.hasIdIn(targetIds)) != targetIds.size()) {
} throw new EntityNotFoundException(Target.class, targetIds);
}
}); });
actionRepository.switchStatus(Status.CANCELED, targetIds, false, Status.SCHEDULED); actionRepository.switchStatus(Status.CANCELED, targetIds, false, Status.SCHEDULED);
} else { } else {
@@ -776,24 +773,21 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
@Override @Override
public Optional<Action> findAction(final long actionId) { public Optional<Action> findAction(final long actionId) {
return actionRepository return actionRepository.findById(actionId)
.findById(actionId)
.filter(action -> targetRepository.exists(TargetSpecifications.hasId(action.getTarget().getId()))) .filter(action -> targetRepository.exists(TargetSpecifications.hasId(action.getTarget().getId())))
.map(JpaAction.class::cast); .map(JpaAction.class::cast);
} }
@Override @Override
public Optional<Action> findActionWithDetails(final long actionId) { public Optional<Action> findActionWithDetails(final long actionId) {
return actionRepository return actionRepository.findWithDetailsById(actionId)
.findWithDetailsById(actionId)
.filter(action -> targetRepository.exists(TargetSpecifications.hasId(action.getTarget().getId()))); .filter(action -> targetRepository.exists(TargetSpecifications.hasId(action.getTarget().getId())));
} }
@Override @Override
public Slice<Action> findActionsByTarget(final String controllerId, final Pageable pageable) { public Slice<Action> findActionsByTarget(final String controllerId, final Pageable pageable) {
assertTargetReadAllowed(controllerId); assertTargetReadAllowed(controllerId);
return actionRepository return actionRepository.findAll(ActionSpecifications.byTargetControllerId(controllerId), pageable)
.findAll(ActionSpecifications.byTargetControllerId(controllerId), pageable)
.map(Action.class::cast); .map(Action.class::cast);
} }
@@ -853,8 +847,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
@Retryable(include = { @Retryable(include = {
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY)) ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Action forceTargetAction(final long actionId) { public Action forceTargetAction(final long actionId) {
final JpaAction action = actionRepository.findById(actionId) final JpaAction action = actionRepository.findById(actionId).map(this::assertTargetUpdateAllowed)
.map(this::assertTargetUpdateAllowed)
.orElseThrow(() -> new EntityNotFoundException(Action.class, actionId)); .orElseThrow(() -> new EntityNotFoundException(Action.class, actionId));
if (!action.isForcedOrTimeForced()) { if (!action.isForcedOrTimeForced()) {
@@ -878,7 +871,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
return actionStatusRepository.countByActionId(actionId); 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 // and UI which is to be removed
@Override @Override
public Page<String> findMessagesByActionStatusId(final Pageable pageable, final long actionStatusId) { 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); 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 @Override
public Slice<Action> findActionsAll(final Pageable pageable) { public Slice<Action> findActionsAll(final Pageable pageable) {
return JpaManagementHelper.findAllWithoutCountBySpec(actionRepository, pageable, null); 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 @Override
public Slice<Action> findActions(final String rsqlParam, final Pageable pageable) { public Slice<Action> findActions(final String rsqlParam, final Pageable pageable) {
final List<Specification<JpaAction>> specList = List.of( final List<Specification<JpaAction>> specList = List.of(
@@ -983,8 +973,8 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
public boolean hasPendingCancellations(final Long targetId) { public boolean hasPendingCancellations(final Long targetId) {
// target access checked in assertTargetReadAllowed // target access checked in assertTargetReadAllowed
assertTargetReadAllowed(targetId); assertTargetReadAllowed(targetId);
return actionRepository.exists( return actionRepository
ActionSpecifications.byTargetIdAndIsActiveAndStatus(targetId, Action.Status.CANCELING)); .exists(ActionSpecifications.byTargetIdAndIsActiveAndStatus(targetId, Action.Status.CANCELING));
} }
private static String getQueryForDeleteActionsByStatusAndLastModifiedBeforeString(final Database database) { private static String getQueryForDeleteActionsByStatusAndLastModifiedBeforeString(final Database database) {
@@ -1039,29 +1029,33 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
@Override @Override
@Transactional @Transactional
public void cancelActionsForDistributionSet( public void cancelActionsForDistributionSet(final CancelationType cancelationType,
final CancelationType cancelationType, final DistributionSet distributionSet) { final DistributionSet distributionSet) {
actionRepository actionRepository.findAll(ActionSpecifications
.findAll(ActionSpecifications.byDistributionSetIdAndActiveAndStatusIsNot(distributionSet.getId(), Status.CANCELING)) .byDistributionSetIdAndActiveAndStatusIsNot(distributionSet.getId(), Status.CANCELING))
.forEach(action -> { .forEach(action -> {
try { try {
assertTargetUpdateAllowed(action); assertTargetUpdateAllowed(action);
cancelAction(action.getId()); cancelAction(action.getId());
LOG.debug("Action {} canceled", action.getId()); LOG.debug("Action {} canceled", action.getId());
} catch (final InsufficientPermissionException e) { } 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) { if (cancelationType == CancelationType.FORCE) {
actionRepository actionRepository.findAll(ActionSpecifications.byDistributionSetIdAndActive(distributionSet.getId()))
.findAll(ActionSpecifications.byDistributionSetIdAndActive(distributionSet.getId()))
.forEach(action -> { .forEach(action -> {
try { try {
assertTargetUpdateAllowed(action); assertTargetUpdateAllowed(action);
forceQuitAction(action.getId()); forceQuitAction(action.getId());
LOG.debug("Action {} force canceled", action.getId()); LOG.debug("Action {} force canceled", action.getId());
} catch (final InsufficientPermissionException e) { } 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) { 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); throw new EntityNotFoundException(Action.class, action);
} });
return 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;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType; import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation.CancelationType;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidationCount; import org.eclipse.hawkbit.repository.model.DistributionSetInvalidationCount;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware; import org.eclipse.hawkbit.tenancy.TenantAware;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -50,13 +51,14 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet
private final RepositoryProperties repositoryProperties; private final RepositoryProperties repositoryProperties;
private final TenantAware tenantAware; private final TenantAware tenantAware;
private final LockRegistry lockRegistry; private final LockRegistry lockRegistry;
private final SystemSecurityContext systemSecurityContext;
public JpaDistributionSetInvalidationManagement(final DistributionSetManagement distributionSetManagement, public JpaDistributionSetInvalidationManagement(final DistributionSetManagement distributionSetManagement,
final RolloutManagement rolloutManagement, final DeploymentManagement deploymentManagement, final RolloutManagement rolloutManagement, final DeploymentManagement deploymentManagement,
final TargetFilterQueryManagement targetFilterQueryManagement, final ActionRepository actionRepository, final TargetFilterQueryManagement targetFilterQueryManagement, final ActionRepository actionRepository,
final PlatformTransactionManager txManager, final PlatformTransactionManager txManager, final RepositoryProperties repositoryProperties,
final RepositoryProperties repositoryProperties, final TenantAware tenantAware, final TenantAware tenantAware, final LockRegistry lockRegistry,
final LockRegistry lockRegistry) { final SystemSecurityContext systemSecurityContext) {
this.distributionSetManagement = distributionSetManagement; this.distributionSetManagement = distributionSetManagement;
this.rolloutManagement = rolloutManagement; this.rolloutManagement = rolloutManagement;
this.deploymentManagement = deploymentManagement; this.deploymentManagement = deploymentManagement;
@@ -66,13 +68,13 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet
this.repositoryProperties = repositoryProperties; this.repositoryProperties = repositoryProperties;
this.tenantAware = tenantAware; this.tenantAware = tenantAware;
this.lockRegistry = lockRegistry; this.lockRegistry = lockRegistry;
this.systemSecurityContext = systemSecurityContext;
} }
@Override @Override
public void invalidateDistributionSet(final DistributionSetInvalidation distributionSetInvalidation) { public void invalidateDistributionSet(final DistributionSetInvalidation distributionSetInvalidation) {
LOG.debug("Invalidate distribution sets {}", distributionSetInvalidation.getDistributionSetIds()); LOG.debug("Invalidate distribution sets {}", distributionSetInvalidation.getDistributionSetIds());
final String tenant = tenantAware.getCurrentTenant(); final String tenant = tenantAware.getCurrentTenant();
if (shouldRolloutsBeCanceled(distributionSetInvalidation.getCancelationType(), if (shouldRolloutsBeCanceled(distributionSetInvalidation.getCancelationType(),
distributionSetInvalidation.isCancelRollouts())) { distributionSetInvalidation.isCancelRollouts())) {
final String handlerId = JpaRolloutManagement.createRolloutLockKey(tenant); 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 // no lock is needed as no rollout will be stopped
invalidateDistributionSetsInTransaction(distributionSetInvalidation, tenant); invalidateDistributionSetsInTransaction(distributionSetInvalidation, tenant);
} }
} }
private void invalidateDistributionSetsInTransaction(final DistributionSetInvalidation distributionSetInvalidation, private void invalidateDistributionSetsInTransaction(final DistributionSetInvalidation distributionSetInvalidation,
@@ -110,17 +113,25 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet
final boolean cancelRollouts) { final boolean cancelRollouts) {
final DistributionSet set = distributionSetManagement.getValidAndComplete(setId); final DistributionSet set = distributionSetManagement.getValidAndComplete(setId);
distributionSetManagement.invalidate(set); 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)) { if (shouldRolloutsBeCanceled(cancelationType, cancelRollouts)) {
LOG.debug("Cancel rollouts after ds invalidation. ID: {}", setId);
rolloutManagement.cancelRolloutsForDistributionSet(set); rolloutManagement.cancelRolloutsForDistributionSet(set);
} }
if (cancelationType != CancelationType.NONE) { // Do run as system to ensure all actions (even invisible) are canceled due to invalidation.
deploymentManagement.cancelActionsForDistributionSet(cancelationType, set); 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, private static boolean shouldRolloutsBeCanceled(final CancelationType cancelationType,
@@ -131,13 +142,16 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet
@Override @Override
public DistributionSetInvalidationCount countEntitiesForInvalidation( public DistributionSetInvalidationCount countEntitiesForInvalidation(
final DistributionSetInvalidation distributionSetInvalidation) { final DistributionSetInvalidation distributionSetInvalidation) {
final Collection<Long> setIds = distributionSetInvalidation.getDistributionSetIds(); return systemSecurityContext.runAsSystem(() -> {
final long rolloutsCount = shouldRolloutsBeCanceled(distributionSetInvalidation.getCancelationType(), final Collection<Long> setIds = distributionSetInvalidation.getDistributionSetIds();
distributionSetInvalidation.isCancelRollouts()) ? countRolloutsForInvalidation(setIds) : 0; final long rolloutsCount = shouldRolloutsBeCanceled(distributionSetInvalidation.getCancelationType(),
final long autoAssignmentsCount = countAutoAssignmentsForInvalidation(setIds); distributionSetInvalidation.isCancelRollouts()) ? countRolloutsForInvalidation(setIds) : 0;
final long actionsCount = countActionsForInvalidation(setIds, distributionSetInvalidation.getCancelationType()); 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) { private long countRolloutsForInvalidation(final Collection<Long> setIds) {
@@ -163,7 +177,9 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet
} }
private long countActionsForSoftInvalidation(final Collection<Long> setIds) { private long countActionsForSoftInvalidation(final Collection<Long> setIds) {
return setIds.stream().mapToLong(distributionSet -> actionRepository return setIds.stream()
.countByDistributionSetIdAndActiveIsTrueAndStatusIsNot(distributionSet, Status.CANCELING)).sum(); .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) { public void invalidate(final DistributionSet distributionSet) {
final JpaDistributionSet jpaSet = (JpaDistributionSet) distributionSet; final JpaDistributionSet jpaSet = (JpaDistributionSet) distributionSet;
jpaSet.invalidate(); jpaSet.invalidate();
distributionSetRepository.save(AccessController.Operation.DELETE, jpaSet); distributionSetRepository.save(jpaSet);
} }
private JpaDistributionSet getById(final long id) { 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 com.google.common.collect.Lists;
import static org.eclipse.hawkbit.repository.jpa.JpaManagementHelper.combineWithAnd;
/** /**
* JPA implementation of {@link TargetManagement}. * JPA implementation of {@link TargetManagement}.
* *
@@ -130,7 +132,6 @@ public class JpaTargetManagement implements TargetManagement {
private final Database database; private final Database database;
public JpaTargetManagement(final EntityManager entityManager, public JpaTargetManagement(final EntityManager entityManager,
final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement, final DistributionSetManagement distributionSetManagement, final QuotaManagement quotaManagement,
final TargetRepository targetRepository, final TargetTypeRepository targetTypeRepository, final TargetRepository targetRepository, final TargetTypeRepository targetTypeRepository,
@@ -138,8 +139,8 @@ public class JpaTargetManagement implements TargetManagement {
final RolloutGroupRepository rolloutGroupRepository, final RolloutGroupRepository rolloutGroupRepository,
final TargetFilterQueryRepository targetFilterQueryRepository, final TargetFilterQueryRepository targetFilterQueryRepository,
final TargetTagRepository targetTagRepository, final EventPublisherHolder eventPublisherHolder, final TargetTagRepository targetTagRepository, final EventPublisherHolder eventPublisherHolder,
final TenantAware tenantAware, final TenantAware tenantAware, final VirtualPropertyReplacer virtualPropertyReplacer,
final VirtualPropertyReplacer virtualPropertyReplacer, final Database database) { final Database database) {
this.entityManager = entityManager; this.entityManager = entityManager;
this.distributionSetManagement = distributionSetManagement; this.distributionSetManagement = distributionSetManagement;
this.quotaManagement = quotaManagement; this.quotaManagement = quotaManagement;
@@ -161,8 +162,7 @@ public class JpaTargetManagement implements TargetManagement {
} }
private JpaTarget getByControllerIdAndThrowIfNotFound(final String controllerId) { private JpaTarget getByControllerIdAndThrowIfNotFound(final String controllerId) {
return targetRepository return targetRepository.findOne(TargetSpecifications.hasControllerId(controllerId))
.findOne(TargetSpecifications.hasControllerId(controllerId))
.orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId)); .orElseThrow(() -> new EntityNotFoundException(Target.class, controllerId));
} }
@@ -255,6 +255,9 @@ public class JpaTargetManagement implements TargetManagement {
final JpaTarget target = JpaManagementHelper.touch(entityManager, targetRepository, final JpaTarget target = JpaManagementHelper.touch(entityManager, targetRepository,
getByControllerIdAndThrowIfNotFound(controllerId)); getByControllerIdAndThrowIfNotFound(controllerId));
targetRepository.getAccessController()
.ifPresent(acm -> acm.assertOperationAllowed(AccessController.Operation.UPDATE, target));
targetMetadataRepository.deleteById(metadata.getId()); targetMetadataRepository.deleteById(metadata.getId());
// target update event is set to ignore "lastModifiedAt" field, so it is // target update event is set to ignore "lastModifiedAt" field, so it is
// not send automatically within the touch() method // not send automatically within the touch() method
@@ -312,17 +315,14 @@ public class JpaTargetManagement implements TargetManagement {
.orElseThrow(() -> new EntityNotFoundException(TargetFilterQuery.class, targetFilterQueryId)); .orElseThrow(() -> new EntityNotFoundException(TargetFilterQuery.class, targetFilterQueryId));
return JpaManagementHelper.findAllWithoutCountBySpec(targetRepository, pageable, return JpaManagementHelper.findAllWithoutCountBySpec(targetRepository, pageable,
List.of( List.of(RSQLUtility.buildRsqlSpecification(targetFilterQuery.getQuery(), TargetFields.class,
RSQLUtility.buildRsqlSpecification(targetFilterQuery.getQuery(), TargetFields.class, virtualPropertyReplacer, database)));
virtualPropertyReplacer, database)));
} }
@Override @Override
public Slice<Target> findByRsql(final Pageable pageable, final String targetFilterQuery) { public Slice<Target> findByRsql(final Pageable pageable, final String targetFilterQuery) {
return JpaManagementHelper.findAllWithoutCountBySpec(targetRepository, pageable, return JpaManagementHelper.findAllWithoutCountBySpec(targetRepository, pageable, List.of(RSQLUtility
List.of( .buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer, database)));
RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class,
virtualPropertyReplacer, database)));
} }
@Override @Override
@@ -481,7 +481,7 @@ public class JpaTargetManagement implements TargetManagement {
final TargetTag tag = targetTagRepository.findByNameEquals(tagName) final TargetTag tag = targetTagRepository.findByNameEquals(tagName)
.orElseThrow(() -> new EntityNotFoundException(TargetTag.class, tagName)); .orElseThrow(() -> new EntityNotFoundException(TargetTag.class, tagName));
final List<JpaTarget> allTargets = targetRepository final List<JpaTarget> allTargets = targetRepository
.findAll(AccessController.Operation.UPDATE, TargetSpecifications.byControllerIdWithTagsInJoin(controllerIds)); .findAll(TargetSpecifications.byControllerIdWithTagsInJoin(controllerIds));
if (allTargets.size() < controllerIds.size()) { if (allTargets.size() < controllerIds.size()) {
throw new EntityNotFoundException(Target.class, controllerIds, throw new EntityNotFoundException(Target.class, controllerIds,
allTargets.stream().map(Target::getControllerId).toList()); allTargets.stream().map(Target::getControllerId).toList());
@@ -492,6 +492,7 @@ public class JpaTargetManagement implements TargetManagement {
// all are already assigned -> unassign // all are already assigned -> unassign
if (alreadyAssignedTargets.size() == allTargets.size()) { if (alreadyAssignedTargets.size() == allTargets.size()) {
alreadyAssignedTargets.forEach(target -> target.removeTag(tag)); alreadyAssignedTargets.forEach(target -> target.removeTag(tag));
return new TargetTagAssignmentResult(0, Collections.emptyList(), return new TargetTagAssignmentResult(0, Collections.emptyList(),
Collections.unmodifiableList(alreadyAssignedTargets), tag); Collections.unmodifiableList(alreadyAssignedTargets), tag);
@@ -501,9 +502,7 @@ public class JpaTargetManagement implements TargetManagement {
// some or none are assigned -> assign // some or none are assigned -> assign
allTargets.forEach(target -> target.addTag(tag)); allTargets.forEach(target -> target.addTag(tag));
final TargetTagAssignmentResult result = new TargetTagAssignmentResult(alreadyAssignedTargets.size(), final TargetTagAssignmentResult result = new TargetTagAssignmentResult(alreadyAssignedTargets.size(),
Collections targetRepository.saveAll(allTargets), Collections.emptyList(), tag);
.unmodifiableList(allTargets.stream().map(targetRepository::save).collect(Collectors.toList())),
Collections.emptyList(), tag);
// no reason to persist the tag // no reason to persist the tag
entityManager.detach(tag); entityManager.detach(tag);
@@ -528,7 +527,7 @@ public class JpaTargetManagement implements TargetManagement {
targetsWithoutSameType.forEach(target -> target.setTargetType(type)); targetsWithoutSameType.forEach(target -> target.setTargetType(type));
final TargetTypeAssignmentResult result = new TargetTypeAssignmentResult(targetsWithSameType.size(), 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 // no reason to persist the type
entityManager.detach(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)) 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) { public List<Target> assignTag(final Collection<String> controllerIds, final long tagId) {
final List<JpaTarget> allTargets = targetRepository final List<JpaTarget> allTargets = targetRepository
.findAll(AccessController.Operation.UPDATE, TargetSpecifications.byControllerIdWithTagsInJoin(controllerIds)); .findAll(TargetSpecifications.byControllerIdWithTagsInJoin(controllerIds));
if (allTargets.size() < controllerIds.size()) { if (allTargets.size() < controllerIds.size()) {
throw new EntityNotFoundException(Target.class, controllerIds, throw new EntityNotFoundException(Target.class, controllerIds,
allTargets.stream().map(Target::getControllerId).toList()); allTargets.stream().map(Target::getControllerId).toList());
} }
targetRepository.getAccessController()
.ifPresent(acm -> acm.assertOperationAllowed(AccessController.Operation.UPDATE, allTargets));
final JpaTargetTag tag = targetTagRepository.findById(tagId) final JpaTargetTag tag = targetTagRepository.findById(tagId)
.orElseThrow(() -> new EntityNotFoundException(TargetTag.class, 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)) ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public Target assignType(final String controllerId, final Long targetTypeId) { public Target assignType(final String controllerId, final Long targetTypeId) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId); final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
targetRepository.getAccessController().ifPresent(acm -> {
acm.assertOperationAllowed(AccessController.Operation.UPDATE, target);
});
final JpaTargetType targetType = getTargetTypeByIdAndThrowIfNotFound(targetTypeId); final JpaTargetType targetType = getTargetTypeByIdAndThrowIfNotFound(targetTypeId);
target.setTargetType(targetType); target.setTargetType(targetType);
return targetRepository.save(target); return targetRepository.save(target);
@@ -626,7 +633,7 @@ public class JpaTargetManagement implements TargetManagement {
@Override @Override
public Slice<Target> findByFilterOrderByLinkedDistributionSet(final Pageable pageable, 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 // remove default sort from pageable to not overwrite sorted spec
final OffsetBasedPageRequest unsortedPage = new OffsetBasedPageRequest(pageable.getOffset(), final OffsetBasedPageRequest unsortedPage = new OffsetBasedPageRequest(pageable.getOffset(),
pageable.getPageSize(), Sort.unsorted()); pageable.getPageSize(), Sort.unsorted());
@@ -661,41 +668,42 @@ public class JpaTargetManagement implements TargetManagement {
@Override @Override
public Slice<Target> findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(final Pageable pageRequest, 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 DistributionSet jpaDistributionSet = distributionSetManagement.getOrElseThrowException(distributionSetId);
final Long distSetTypeId = jpaDistributionSet.getType().getId(); final Long distSetTypeId = jpaDistributionSet.getType().getId();
return targetRepository.findAllWithoutCount( return targetRepository
AccessController.Operation.UPDATE, .findAllWithoutCount(AccessController.Operation.UPDATE,
JpaManagementHelper.combineWithAnd(List.of( combineWithAnd(List.of(
RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer, RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class,
database), virtualPropertyReplacer, database),
TargetSpecifications.hasNotDistributionSetInActions(distributionSetId), TargetSpecifications.hasNotDistributionSetInActions(distributionSetId),
TargetSpecifications.isCompatibleWithDistributionSetType(distSetTypeId))), TargetSpecifications.isCompatibleWithDistributionSetType(distSetTypeId))),
pageRequest).map(Target.class::cast); pageRequest)
.map(Target.class::cast);
} }
@Override @Override
public Slice<Target> findByTargetFilterQueryAndNotInRolloutGroupsAndCompatibleAndUpdatable( public Slice<Target> findByTargetFilterQueryAndNotInRolloutGroupsAndCompatibleAndUpdatable(
final Pageable pageRequest, final Collection<Long> groups, final String targetFilterQuery, final Pageable pageRequest, final Collection<Long> groups, final String targetFilterQuery,
final DistributionSetType dsType) { final DistributionSetType dsType) {
return targetRepository.findAllWithoutCount( return targetRepository
AccessController.Operation.UPDATE, .findAllWithoutCount(AccessController.Operation.UPDATE,
JpaManagementHelper.combineWithAnd(List.of( combineWithAnd(List.of(
RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer, RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class,
database), virtualPropertyReplacer, database),
TargetSpecifications.isNotInRolloutGroups(groups), TargetSpecifications.isNotInRolloutGroups(groups),
TargetSpecifications.isCompatibleWithDistributionSetType(dsType.getId()))), TargetSpecifications.isCompatibleWithDistributionSetType(dsType.getId()))),
pageRequest).map(Target.class::cast); pageRequest)
.map(Target.class::cast);
} }
@Override @Override
public Slice<Target> findByFailedRolloutAndNotInRolloutGroups(Pageable pageRequest, Collection<Long> groups, public Slice<Target> findByFailedRolloutAndNotInRolloutGroups(Pageable pageRequest, Collection<Long> groups,
String rolloutId) { String rolloutId) {
final List<Specification<JpaTarget>> specList = Arrays.asList( final List<Specification<JpaTarget>> specList = Arrays.asList(
TargetSpecifications.failedActionsForRollout(rolloutId), TargetSpecifications.failedActionsForRollout(rolloutId),
TargetSpecifications.isNotInRolloutGroups(groups) TargetSpecifications.isNotInRolloutGroups(groups));
);
return JpaManagementHelper.findAllWithCountBySpec(targetRepository, pageRequest, specList); return JpaManagementHelper.findAllWithCountBySpec(targetRepository, pageRequest, specList);
} }
@@ -712,34 +720,34 @@ public class JpaTargetManagement implements TargetManagement {
@Override @Override
public long countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(final Collection<Long> groups, public long countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(final Collection<Long> groups,
final String targetFilterQuery, final DistributionSetType dsType) { final String targetFilterQuery, final DistributionSetType dsType) {
return targetRepository.count(AccessController.Operation.UPDATE, JpaManagementHelper.combineWithAnd( return targetRepository.count(AccessController.Operation.UPDATE,
List.of( combineWithAnd(List.of(
RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer, RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class,
database), virtualPropertyReplacer, database),
TargetSpecifications.isNotInRolloutGroups(groups), TargetSpecifications.isNotInRolloutGroups(groups),
TargetSpecifications.isCompatibleWithDistributionSetType(dsType.getId())))); TargetSpecifications.isCompatibleWithDistributionSetType(dsType.getId()))));
} }
@Override @Override
public long countByFailedRolloutAndNotInRolloutGroups(Collection<Long> groups, String rolloutId) { public long countByFailedRolloutAndNotInRolloutGroups(Collection<Long> groups, String rolloutId) {
final List<Specification<JpaTarget>> specList = Arrays.asList( final List<Specification<JpaTarget>> specList = Arrays.asList(
TargetSpecifications.failedActionsForRollout(rolloutId), TargetSpecifications.failedActionsForRollout(rolloutId),
TargetSpecifications.isNotInRolloutGroups(groups)); TargetSpecifications.isNotInRolloutGroups(groups));
return JpaManagementHelper.countBySpec(targetRepository, specList); return JpaManagementHelper.countBySpec(targetRepository, specList);
} }
@Override @Override
public long countByRsqlAndNonDSAndCompatibleAndUpdatable(final long distributionSetId, public long countByRsqlAndNonDSAndCompatibleAndUpdatable(final long distributionSetId,
final String targetFilterQuery) { final String targetFilterQuery) {
final DistributionSet jpaDistributionSet = distributionSetManagement.getOrElseThrowException(distributionSetId); final DistributionSet jpaDistributionSet = distributionSetManagement.getOrElseThrowException(distributionSetId);
final Long distSetTypeId = jpaDistributionSet.getType().getId(); final Long distSetTypeId = jpaDistributionSet.getType().getId();
return targetRepository.count(AccessController.Operation.UPDATE, JpaManagementHelper.combineWithAnd( return targetRepository.count(AccessController.Operation.UPDATE,
List.of( combineWithAnd(List.of(
RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer, RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class,
database), virtualPropertyReplacer, database),
TargetSpecifications.hasNotDistributionSetInActions(distributionSetId), TargetSpecifications.hasNotDistributionSetInActions(distributionSetId),
TargetSpecifications.isCompatibleWithDistributionSetType(distSetTypeId)))); TargetSpecifications.isCompatibleWithDistributionSetType(distSetTypeId))));
} }
@@ -798,27 +806,38 @@ public class JpaTargetManagement implements TargetManagement {
@Override @Override
public long countByRsql(final String targetFilterQuery) { public long countByRsql(final String targetFilterQuery) {
return JpaManagementHelper.countBySpec( return JpaManagementHelper.countBySpec(targetRepository, List.of(RSQLUtility
targetRepository, .buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer, database)));
List.of(
RSQLUtility.buildRsqlSpecification(
targetFilterQuery, TargetFields.class, virtualPropertyReplacer, database)));
} }
@Override @Override
public long countByRsqlAndCompatible(final String targetFilterQuery, final Long distributionSetId) { public long countByRsqlAndUpdatable(String targetFilterQuery) {
final List<Specification<JpaTarget>> specList = List.of( final List<Specification<JpaTarget>> specList = List.of(RSQLUtility.buildRsqlSpecification(targetFilterQuery,
RSQLUtility.buildRsqlSpecification(targetFilterQuery, TargetFields.class, virtualPropertyReplacer, TargetFields.class, virtualPropertyReplacer, database));
database), return targetRepository.count(AccessController.Operation.UPDATE, combineWithAnd(specList));
TargetSpecifications.isCompatibleWithDistributionSetType(distributionSetId)); }
@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); 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 @Override
public long countByFailedInRollout(final String rolloutId, final Long dsTypeId) { public long countByFailedInRollout(final String rolloutId, final Long dsTypeId) {
final List<Specification<JpaTarget>> specList = List.of( final List<Specification<JpaTarget>> specList = List
TargetSpecifications.failedActionsForRollout(rolloutId)); .of(TargetSpecifications.failedActionsForRollout(rolloutId));
return JpaManagementHelper.countBySpec(targetRepository, specList); return JpaManagementHelper.countBySpec(targetRepository, specList);
} }
@@ -856,6 +875,8 @@ public class JpaTargetManagement implements TargetManagement {
@Override @Override
public void requestControllerAttributes(final String controllerId) { public void requestControllerAttributes(final String controllerId) {
final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId); final JpaTarget target = getByControllerIdAndThrowIfNotFound(controllerId);
targetRepository.getAccessController()
.ifPresent(acm -> acm.assertOperationAllowed(AccessController.Operation.UPDATE, target));
target.setRequestControllerAttributes(true); target.setRequestControllerAttributes(true);
eventPublisherHolder.getEventPublisher() eventPublisherHolder.getEventPublisher()
.publishEvent(new TargetAttributesRequestedEvent(tenantAware.getCurrentTenant(), target.getId(), .publishEvent(new TargetAttributesRequestedEvent(tenantAware.getCurrentTenant(), target.getId(),
@@ -876,7 +897,7 @@ public class JpaTargetManagement implements TargetManagement {
} }
@Override @Override
public boolean isTargetMatchingQueryAndDSNotAssignedAndCompatible(final String controllerId, public boolean isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(final String controllerId,
final long distributionSetId, final String targetFilterQuery) { final long distributionSetId, final String targetFilterQuery) {
RSQLUtility.validateRsqlFor(targetFilterQuery, TargetFields.class); RSQLUtility.validateRsqlFor(targetFilterQuery, TargetFields.class);
final DistributionSet ds = distributionSetManagement.get(distributionSetId) final DistributionSet ds = distributionSetManagement.get(distributionSetId)
@@ -891,7 +912,7 @@ public class JpaTargetManagement implements TargetManagement {
final Specification<JpaTarget> combinedSpecification = Objects final Specification<JpaTarget> combinedSpecification = Objects
.requireNonNull(SpecificationsBuilder.combineWithAnd(specList)); .requireNonNull(SpecificationsBuilder.combineWithAnd(specList));
return targetRepository.exists(combinedSpecification); return targetRepository.exists(AccessController.Operation.UPDATE, combinedSpecification);
} }
@Override @Override

View File

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

View File

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

View File

@@ -203,18 +203,18 @@ class DistributionSetInvalidationManagementTest extends AbstractJpaIntegrationTe
} }
@Test @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" }) @WithUser(authorities = { "READ_REPOSITORY", "UPDATE_REPOSITORY" })
void verifyInvalidateWithReadAndUpdateRepoAuthority() { void verifyInvalidateWithReadAndUpdateRepoAuthority() {
final InvalidationTestData invalidationTestData = systemSecurityContext final InvalidationTestData invalidationTestData = systemSecurityContext
.runAsSystem(() -> createInvalidationTestData("verifyInvalidateWithUpdateRepoAuthority")); .runAsSystem(() -> createInvalidationTestData("verifyInvalidateWithUpdateRepoAuthority"));
assertThatExceptionOfType(InsufficientPermissionException.class) distributionSetInvalidationManagement.invalidateDistributionSet(new DistributionSetInvalidation(
.as("Insufficient permission exception expected") Collections.singletonList(invalidationTestData.getDistributionSet().getId()), CancelationType.NONE,
.isThrownBy(() -> distributionSetInvalidationManagement false));
.invalidateDistributionSet(new DistributionSetInvalidation( assertThat(
Collections.singletonList(invalidationTestData.getDistributionSet().getId()), distributionSetRepository.findById(invalidationTestData.getDistributionSet().getId()).get().isValid())
CancelationType.NONE, false))); .isFalse();
} }
@Test @Test

View File

@@ -1267,7 +1267,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
final DistributionSet ds = testdataFactory.createDistributionSet(); final DistributionSet ds = testdataFactory.createDistributionSet();
final String filter = "metadata.key1==target1-value1"; final String filter = "metadata.key1==target1-value1";
assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target.getControllerId(), assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(target.getControllerId(),
ds.getId(), filter)).isTrue(); ds.getId(), filter)).isTrue();
} }
@@ -1278,7 +1278,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
final DistributionSet ds = testdataFactory.createDistributionSet(); final DistributionSet ds = testdataFactory.createDistributionSet();
final String filter = "metadata.key==not_existing"; final String filter = "metadata.key==not_existing";
assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target.getControllerId(), assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(target.getControllerId(),
ds.getId(), filter)).isFalse(); ds.getId(), filter)).isFalse();
} }
@@ -1292,7 +1292,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
assignDistributionSet(ds2, target); assignDistributionSet(ds2, target);
final String filter = "name==*"; final String filter = "name==*";
assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target.getControllerId(), assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(target.getControllerId(),
ds1.getId(), filter)).isFalse(); ds1.getId(), filter)).isFalse();
} }
@@ -1303,7 +1303,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
final Target target = testdataFactory.createTarget("target", "target", type.getId()); final Target target = testdataFactory.createTarget("target", "target", type.getId());
final DistributionSet ds = testdataFactory.createDistributionSet(); final DistributionSet ds = testdataFactory.createDistributionSet();
assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target.getControllerId(), assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(target.getControllerId(),
ds.getId(), "name==*")).isFalse(); ds.getId(), "name==*")).isFalse();
} }
@@ -1314,9 +1314,9 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
final Long ds = testdataFactory.createDistributionSet().getId(); final Long ds = testdataFactory.createDistributionSet().getId();
assertThatExceptionOfType(RSQLParameterSyntaxException.class).isThrownBy(() -> targetManagement assertThatExceptionOfType(RSQLParameterSyntaxException.class).isThrownBy(() -> targetManagement
.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target, ds, "invalid_syntax")); .isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(target, ds, "invalid_syntax"));
assertThatExceptionOfType(RSQLParameterUnsupportedFieldException.class).isThrownBy(() -> targetManagement assertThatExceptionOfType(RSQLParameterUnsupportedFieldException.class).isThrownBy(() -> targetManagement
.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target, ds, "invalid_field==1")); .isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(target, ds, "invalid_field==1"));
} }
@Test @Test
@@ -1324,7 +1324,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
void matchesFilterTargetNotExists() { void matchesFilterTargetNotExists() {
final DistributionSet ds = testdataFactory.createDistributionSet(); final DistributionSet ds = testdataFactory.createDistributionSet();
assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible("notExisting", ds.getId(), assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable("notExisting", ds.getId(),
"name==*")).isFalse(); "name==*")).isFalse();
} }
@@ -1334,7 +1334,7 @@ class TargetManagementTest extends AbstractJpaIntegrationTest {
final String target = testdataFactory.createTarget().getControllerId(); final String target = testdataFactory.createTarget().getControllerId();
assertThatExceptionOfType(EntityNotFoundException.class).isThrownBy( assertThatExceptionOfType(EntityNotFoundException.class).isThrownBy(
() -> targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatible(target, 123, "name==*")); () -> targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(target, 123, "name==*"));
} }
private void validateFoundTargetsByRsql(final String rsqlFilter, final String... controllerIds) { 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) { private Long getTotalTargets(final String filterQuery, final Long distSetTypeId) {
// TODO AC - Check for updatable targets only
if (StringUtils.isEmpty(filterQuery)) { if (StringUtils.isEmpty(filterQuery)) {
return null; return null;
} }
if (distSetTypeId == 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() { private boolean isSimpleGroupsTabSelected() {