Fix action delete access control - to require only target update (not delete also) (#2767)
Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
@@ -320,22 +320,22 @@ public interface DeploymentManagement extends PermissionSupport {
|
||||
void deleteActionsByIds(List<Long> actionIds);
|
||||
|
||||
/**
|
||||
* Deletes actions in scope of the target ONLY by list of action ids.
|
||||
* Deletes actions in scope of the controllerId ONLY by list of action ids.
|
||||
*
|
||||
* @param target - target controllerId
|
||||
* @param controllerId - controllerId controllerId
|
||||
* @param actionsIds - list of action ids to be deleted
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('UPDATE_" + SpPermission.TARGET + "')")
|
||||
void deleteTargetActionsByIds(final String target, final List<Long> actionsIds);
|
||||
void deleteTargetActionsByIds(final String controllerId, final List<Long> actionsIds);
|
||||
|
||||
/**
|
||||
* Deletes target actions and leaves the LAST N actions in the action history only.
|
||||
* Deletes controllerId actions and leaves the LAST N actions in the action history only.
|
||||
*
|
||||
* @param target - target controllerId
|
||||
* @param controllerId - controllerId controllerId
|
||||
* @param keepLast - number of actions to be left/kept (NOT deleted)
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('UPDATE_" + SpPermission.TARGET + "')")
|
||||
void deleteOldestTargetActions(final String target, final int keepLast);
|
||||
void deleteOldestTargetActions(final String controllerId, final int keepLast);
|
||||
|
||||
/**
|
||||
* Sets the status of inactive scheduled {@link Action}s for the specified {@link Target}s to {@link Status#CANCELED}
|
||||
@@ -413,5 +413,5 @@ public interface DeploymentManagement extends PermissionSupport {
|
||||
void cancelActionsForDistributionSet(final ActionCancellationType cancelationType, final DistributionSet set);
|
||||
|
||||
@PreAuthorize(SpringEvalExpressions.IS_SYSTEM_CODE)
|
||||
void handleMaxAssignmentsExceeded(Long targetId, Long requested, AssignmentQuotaExceededException ex);
|
||||
void handleMaxAssignmentsExceeded(Long targetId, Long requested, AssignmentQuotaExceededException quotaExceededException);
|
||||
}
|
||||
@@ -385,14 +385,13 @@ public class JpaRolloutExecutor implements RolloutExecutor {
|
||||
}
|
||||
|
||||
private void deleteScheduledActions(final JpaRollout rollout, final Slice<JpaAction> scheduledActions) {
|
||||
final boolean hasScheduledActions = scheduledActions.getNumberOfElements() > 0;
|
||||
|
||||
if (hasScheduledActions) {
|
||||
if (scheduledActions.getNumberOfElements() > 0) {
|
||||
// has scheduled actions - delete them
|
||||
try {
|
||||
final List<Long> actionIds = StreamSupport.stream(scheduledActions.spliterator(), false)
|
||||
.map(Action::getId)
|
||||
.toList();
|
||||
actionRepository.deleteByIdIn(actionIds);
|
||||
actionRepository.deleteAllById(actionIds);
|
||||
afterCommit.afterCommit(() -> EventPublisherHolder.getInstance().getEventPublisher()
|
||||
.publishEvent(new RolloutUpdatedEvent(rollout)));
|
||||
} catch (final RuntimeException e) {
|
||||
|
||||
@@ -80,7 +80,7 @@ public class AccessControllerConfiguration {
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Optional<Specification<JpaAction>> getAccessRules(final Operation operation) {
|
||||
return targetAccessController.getAccessRules(operation).map(targetSpec -> (actionRoot, query, cb) -> {
|
||||
return targetAccessController.getAccessRules(map(operation)).map(targetSpec -> (actionRoot, query, cb) -> {
|
||||
final Join<JpaAction, JpaTarget> targetJoin = actionRoot.join(JpaAction_.target);
|
||||
final EntityType<JpaTarget> targetModel = query.from(JpaTarget.class).getModel();
|
||||
final Root<JpaTarget> targetRoot = (Root<JpaTarget>) Proxy.newProxyInstance(
|
||||
@@ -101,7 +101,15 @@ public class AccessControllerConfiguration {
|
||||
|
||||
@Override
|
||||
public void assertOperationAllowed(final Operation operation, final JpaAction entity) throws InsufficientPermissionException {
|
||||
targetAccessController.assertOperationAllowed(operation, entity.getTarget());
|
||||
targetAccessController.assertOperationAllowed(map(operation), entity.getTarget());
|
||||
}
|
||||
|
||||
// all CREATE/UPDATE/DELETE action operations are mapped to UPDATE_TARGET permissions / actions
|
||||
private static Operation map(final Operation actionOperation) {
|
||||
return switch (actionOperation) {
|
||||
case READ -> Operation.READ;
|
||||
case CREATE, UPDATE, DELETE -> Operation.UPDATE;
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,13 +13,12 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import jakarta.persistence.criteria.JoinType;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.function.TriConsumer;
|
||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||
import org.eclipse.hawkbit.repository.RepositoryConstants;
|
||||
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
||||
@@ -29,6 +28,7 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
|
||||
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
|
||||
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
|
||||
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
|
||||
import org.eclipse.hawkbit.repository.jpa.management.JpaDeploymentManagement.MaxAssignmentsExceededInfo;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaBaseEntity_;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus;
|
||||
@@ -67,7 +67,7 @@ public abstract class AbstractDsAssignmentStrategy {
|
||||
private final BooleanSupplier multiAssignmentsConfig;
|
||||
private final BooleanSupplier confirmationFlowConfig;
|
||||
private final RepositoryProperties repositoryProperties;
|
||||
private final TriConsumer<Long, Long, AssignmentQuotaExceededException> maxAssignmentExceededHandler;
|
||||
private final Consumer<MaxAssignmentsExceededInfo> maxAssignmentExceededHandler;
|
||||
|
||||
@SuppressWarnings("java:S107")
|
||||
AbstractDsAssignmentStrategy(
|
||||
@@ -76,7 +76,7 @@ public abstract class AbstractDsAssignmentStrategy {
|
||||
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
|
||||
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig,
|
||||
final BooleanSupplier confirmationFlowConfig, final RepositoryProperties repositoryProperties,
|
||||
final TriConsumer<Long, Long, AssignmentQuotaExceededException> maxAssignmentExceededHandler) {
|
||||
final Consumer<MaxAssignmentsExceededInfo> maxAssignmentExceededHandler) {
|
||||
this.targetRepository = targetRepository;
|
||||
this.afterCommit = afterCommit;
|
||||
this.actionRepository = actionRepository;
|
||||
@@ -96,7 +96,7 @@ public abstract class AbstractDsAssignmentStrategy {
|
||||
|
||||
// create the action
|
||||
return optTarget.map(target -> {
|
||||
assertActionsPerTargetQuota(target, 1);
|
||||
assertActionsPerTargetQuota(target);
|
||||
final JpaAction actionForTarget = new JpaAction();
|
||||
actionForTarget.setActionType(targetWithActionType.getActionType());
|
||||
actionForTarget.setForcedTime(targetWithActionType.getForceTime());
|
||||
@@ -144,7 +144,7 @@ public abstract class AbstractDsAssignmentStrategy {
|
||||
*
|
||||
* @param targetsIds to override {@link Action}s
|
||||
*/
|
||||
protected List<Long> overrideObsoleteUpdateActions(final Collection<Long> targetsIds) {
|
||||
protected void overrideObsoleteUpdateActions(final Collection<Long> targetsIds) {
|
||||
// Figure out if there are potential target/action combinations that
|
||||
// need to be considered for cancellation
|
||||
final List<JpaAction> activeActions = actionRepository.findAll((root, query, cb) -> {
|
||||
@@ -157,22 +157,18 @@ public abstract class AbstractDsAssignmentStrategy {
|
||||
);
|
||||
});
|
||||
|
||||
final List<Long> targetIds = activeActions.stream().map(action -> {
|
||||
activeActions.forEach(action -> {
|
||||
action.setStatus(Status.CANCELING);
|
||||
|
||||
// document that the status has been retrieved
|
||||
actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELING, System.currentTimeMillis(),
|
||||
RepositoryConstants.SERVER_MESSAGE_PREFIX + "cancel obsolete action due to new update"));
|
||||
actionRepository.save(action);
|
||||
|
||||
return action.getTarget().getId();
|
||||
}).toList();
|
||||
});
|
||||
|
||||
if (!activeActions.isEmpty()) {
|
||||
cancelAssignDistributionSetEvent(Collections.unmodifiableList(activeActions));
|
||||
}
|
||||
|
||||
return targetIds;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,7 +179,7 @@ public abstract class AbstractDsAssignmentStrategy {
|
||||
*
|
||||
* @param targetsIds to override {@link Action}s
|
||||
*/
|
||||
protected List<Long> closeObsoleteUpdateActions(final Collection<Long> targetsIds) {
|
||||
protected void closeObsoleteUpdateActions(final Collection<Long> targetsIds) {
|
||||
// Figure out if there are potential target/action combinations that
|
||||
// need to be considered for cancellation
|
||||
final List<JpaAction> activeActions = actionRepository.findAll((root, query, cb) -> {
|
||||
@@ -195,7 +191,7 @@ public abstract class AbstractDsAssignmentStrategy {
|
||||
);
|
||||
});
|
||||
|
||||
return activeActions.stream().map(action -> {
|
||||
activeActions.forEach(action -> {
|
||||
action.setStatus(Status.CANCELED);
|
||||
action.setActive(false);
|
||||
|
||||
@@ -203,10 +199,7 @@ public abstract class AbstractDsAssignmentStrategy {
|
||||
actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELED, System.currentTimeMillis(),
|
||||
RepositoryConstants.SERVER_MESSAGE_PREFIX + "close obsolete action due to new update"));
|
||||
actionRepository.save(action);
|
||||
|
||||
return action.getTarget().getId();
|
||||
}).toList();
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -237,10 +230,6 @@ public abstract class AbstractDsAssignmentStrategy {
|
||||
*/
|
||||
abstract List<JpaTarget> findTargetsForAssignment(final List<String> controllerIDs, final long distributionSetId);
|
||||
|
||||
/**
|
||||
* @param set
|
||||
* @param targets
|
||||
*/
|
||||
abstract void sendTargetUpdatedEvents(final DistributionSet set, final List<JpaTarget> targets);
|
||||
|
||||
/**
|
||||
@@ -260,9 +249,8 @@ public abstract class AbstractDsAssignmentStrategy {
|
||||
* such actions existed.
|
||||
*
|
||||
* @param targetIds to cancel actions for
|
||||
* @return {@link Set} of {@link Target#getId()}s
|
||||
*/
|
||||
abstract Set<Long> cancelActiveActions(List<List<Long>> targetIds);
|
||||
abstract void cancelActiveActions(List<List<Long>> targetIds);
|
||||
|
||||
/**
|
||||
* Cancels actions that can be canceled (i.e.
|
||||
@@ -274,8 +262,6 @@ public abstract class AbstractDsAssignmentStrategy {
|
||||
*/
|
||||
abstract void closeActiveActions(List<List<Long>> targetIds);
|
||||
|
||||
abstract void sendDeploymentEvents(final DistributionSetAssignmentResult assignmentResult);
|
||||
|
||||
abstract void sendDeploymentEvents(final List<DistributionSetAssignmentResult> assignmentResults);
|
||||
|
||||
private static String getActionMessage(final Action action) {
|
||||
@@ -297,12 +283,12 @@ public abstract class AbstractDsAssignmentStrategy {
|
||||
.publishEvent(new CancelTargetAssignmentEvent(tenant, actions)));
|
||||
}
|
||||
|
||||
private void assertActionsPerTargetQuota(final Target target, final long requested) {
|
||||
private void assertActionsPerTargetQuota(final Target target) {
|
||||
final int quota = quotaManagement.getMaxActionsPerTarget();
|
||||
try {
|
||||
QuotaHelper.assertAssignmentQuota(target.getId(), requested, quota, Action.class, Target.class, actionRepository::countByTargetId);
|
||||
} catch (AssignmentQuotaExceededException ex) {
|
||||
maxAssignmentExceededHandler.accept(target.getId(), requested, ex);
|
||||
QuotaHelper.assertAssignmentQuota(target.getId(), 1, quota, Action.class, Target.class, actionRepository::countByTargetId);
|
||||
} catch (AssignmentQuotaExceededException e) {
|
||||
maxAssignmentExceededHandler.accept(new MaxAssignmentsExceededInfo(target.getId(), 1, e));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -37,8 +38,6 @@ import jakarta.persistence.criteria.Root;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.ListUtils;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.function.TriConsumer;
|
||||
import org.eclipse.hawkbit.repository.ActionFields;
|
||||
import org.eclipse.hawkbit.repository.DeploymentManagement;
|
||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||
@@ -162,6 +161,11 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
this.targetRepository = targetRepository;
|
||||
this.auditorProvider = auditorProvider;
|
||||
this.txManager = txManager;
|
||||
final Consumer<MaxAssignmentsExceededInfo> maxAssignmentsExceededHandler = maxAssignmentsExceededInfo ->
|
||||
handleMaxAssignmentsExceeded(
|
||||
maxAssignmentsExceededInfo.targetId,
|
||||
maxAssignmentsExceededInfo.requested,
|
||||
maxAssignmentsExceededInfo.quotaExceededException);
|
||||
onlineDsAssignmentStrategy = new OnlineDsAssignmentStrategy(targetRepository, afterCommit, actionRepository, actionStatusRepository,
|
||||
quotaManagement, this::isMultiAssignmentsEnabled, this::isConfirmationFlowEnabled, repositoryProperties,
|
||||
maxAssignmentsExceededHandler);
|
||||
@@ -409,14 +413,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
@Transactional
|
||||
public void deleteAction(final long actionId) {
|
||||
log.info("Deleting action {}", actionId);
|
||||
actionRepository.getAccessController().ifPresent(accessController -> {
|
||||
// check update access
|
||||
if (ObjectUtils.isEmpty(actionRepository.findAll(accessController.appendAccessRules(
|
||||
AccessController.Operation.UPDATE, ((root, q, cb) -> cb.equal(root.get(AbstractJpaBaseEntity_.id), actionId)))))) {
|
||||
// could be also InsufficientPermissionException but for security reasons we do not reveal that the entity exists
|
||||
throw new EntityNotFoundException(Action.class, actionId);
|
||||
}
|
||||
});
|
||||
actionRepository.deleteById(actionId);
|
||||
}
|
||||
|
||||
@@ -424,56 +420,41 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
@Transactional
|
||||
public void deleteActionsByRsql(final String rsql) {
|
||||
log.info("Deleting actions matching rsql {}", rsql);
|
||||
final Specification<JpaAction> specification = QLSupport.getInstance().buildSpec(rsql, ActionFields.class);
|
||||
actionRepository.delete(
|
||||
actionRepository.getAccessController()
|
||||
.map(accessController -> accessController.appendAccessRules(AccessController.Operation.UPDATE, specification))
|
||||
.orElse(specification));
|
||||
actionRepository.delete(QLSupport.getInstance().buildSpec(rsql, ActionFields.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteActionsByIds(final List<Long> actionIds) {
|
||||
log.info("Deleting actions with ids {}", actionIds);
|
||||
actionRepository.getAccessController().ifPresent(accessController ->
|
||||
actionRepository.findAll(
|
||||
accessController.appendAccessRules(AccessController.Operation.UPDATE, ActionSpecifications.byIdIn(actionIds))));
|
||||
actionRepository.deleteByIdIn(actionIds);
|
||||
actionRepository.deleteAllById(actionIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteTargetActionsByIds(final String target, final List<Long> actionsIds) {
|
||||
log.info("Delete actions for target {} with action ids {}", target, actionsIds);
|
||||
targetRepository.getAccessController()
|
||||
.ifPresent(accessController ->
|
||||
accessController.assertOperationAllowed(AccessController.Operation.UPDATE, targetRepository.getByControllerId(target)));
|
||||
actionRepository.delete(ActionSpecifications.byControllerIdAndIdIn(target, actionsIds));
|
||||
public void deleteTargetActionsByIds(final String controllerId, final List<Long> actionsIds) {
|
||||
log.info("Delete actions for target {} with action ids {}", controllerId, actionsIds);
|
||||
actionRepository.delete(ActionSpecifications.byControllerIdAndIdIn(controllerId, actionsIds));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public void deleteOldestTargetActions(final String target, final int keepLast) {
|
||||
final JpaTarget jpaTarget = targetRepository.findByControllerId(target)
|
||||
.orElseThrow(EntityNotFoundException::new);
|
||||
public void deleteOldestTargetActions(final String controllerId, final int keepLast) {
|
||||
final JpaTarget target = targetRepository.findByControllerId(controllerId).orElseThrow(EntityNotFoundException::new);
|
||||
// check access to target since deletion will be executed via native query
|
||||
targetRepository.getAccessController().ifPresent(accessController ->
|
||||
accessController.assertOperationAllowed(AccessController.Operation.UPDATE, jpaTarget));
|
||||
|
||||
final long targetActions = actionRepository.countByTargetId(jpaTarget.getId());
|
||||
|
||||
long oldestToDelete;
|
||||
accessController.assertOperationAllowed(AccessController.Operation.UPDATE, target));
|
||||
final long targetActions = actionRepository.countByTargetId(target.getId());
|
||||
if (targetActions > keepLast) {
|
||||
oldestToDelete = targetActions - keepLast;
|
||||
} else {
|
||||
return;
|
||||
final long oldestToDelete = targetActions - keepLast;
|
||||
deleteOldestTargetActions(target.getId(), (int) oldestToDelete);
|
||||
}
|
||||
deleteOldestTargetActions(jpaTarget.getId(), (int) oldestToDelete);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(isolation = Isolation.READ_COMMITTED)
|
||||
@Retryable(retryFor = {
|
||||
ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX, backoff = @Backoff(delay = Constants.TX_RT_DELAY))
|
||||
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
|
||||
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
|
||||
public void cancelInactiveScheduledActionsForTargets(final List<Long> targetIds) {
|
||||
if (!isMultiAssignmentsEnabled()) {
|
||||
targetRepository.getAccessController().ifPresent(v -> {
|
||||
@@ -598,12 +579,11 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMaxAssignmentsExceeded(final Long targetId, final Long requested, final AssignmentQuotaExceededException ex) {
|
||||
maxAssignmentsExceededHandler.accept(targetId, requested, ex);
|
||||
}
|
||||
public record MaxAssignmentsExceededInfo(long targetId, long requested, AssignmentQuotaExceededException quotaExceededException) {}
|
||||
|
||||
private final TriConsumer<Long, Long, AssignmentQuotaExceededException> maxAssignmentsExceededHandler = (targetId, requested, quotaExceededException) -> {
|
||||
@Override
|
||||
public void handleMaxAssignmentsExceeded(
|
||||
final Long targetId, final Long requested, final AssignmentQuotaExceededException quotaExceededException) {
|
||||
int actionsPurgePercentage = getActionsPurgePercentage();
|
||||
int quota = quotaManagement.getMaxActionsPerTarget();
|
||||
if (actionsPurgePercentage > 0 && actionsPurgePercentage < 100) {
|
||||
@@ -622,7 +602,9 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
} else {
|
||||
throw quotaExceededException;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
;
|
||||
|
||||
/**
|
||||
* Deletes the first n target actions of a target
|
||||
@@ -630,7 +612,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
* @param targetId - target id
|
||||
* @param oldestToDelete - number of oldest actions to be deleted
|
||||
*/
|
||||
public void deleteOldestTargetActions(long targetId, int oldestToDelete) {
|
||||
private void deleteOldestTargetActions(long targetId, int oldestToDelete) {
|
||||
// Workaround for the case where JPQL or Criteria API do not support LIMIT
|
||||
log.info("Deleting last {} actions of target {}", oldestToDelete, targetId);
|
||||
final String SQL = "DELETE FROM sp_action WHERE id IN(" +
|
||||
@@ -641,7 +623,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
" LIMIT " + oldestToDelete
|
||||
+ ") AS sub"
|
||||
+ ")";
|
||||
Query query = entityManager.createNativeQuery(SQL);
|
||||
final Query query = entityManager.createNativeQuery(SQL);
|
||||
query.setParameter("target", targetId);
|
||||
query.executeUpdate();
|
||||
}
|
||||
@@ -912,7 +894,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
|
||||
} catch (final AssignmentQuotaExceededException ex) {
|
||||
targetRepository.findByControllerId(controllerId).ifPresentOrElse(
|
||||
// assume requested are always smaller than int size
|
||||
target -> maxAssignmentsExceededHandler.accept(target.getId(), requested, ex),
|
||||
target -> handleMaxAssignmentsExceeded(target.getId(), requested, ex),
|
||||
() -> {
|
||||
throw new EntityNotFoundException(Target.class, controllerId);
|
||||
});
|
||||
|
||||
@@ -9,25 +9,21 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa.management;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.commons.collections4.ListUtils;
|
||||
import org.apache.commons.lang3.function.TriConsumer;
|
||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||
import org.eclipse.hawkbit.repository.RepositoryConstants;
|
||||
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
||||
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
|
||||
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
|
||||
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
|
||||
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
|
||||
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
|
||||
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
|
||||
import org.eclipse.hawkbit.repository.jpa.management.JpaDeploymentManagement.MaxAssignmentsExceededInfo;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
|
||||
@@ -53,7 +49,7 @@ class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
|
||||
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
|
||||
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig,
|
||||
final BooleanSupplier confirmationFlowConfig, final RepositoryProperties repositoryProperties,
|
||||
final TriConsumer<Long, Long, AssignmentQuotaExceededException> maxAssignmentExceededHandler) {
|
||||
final Consumer<MaxAssignmentsExceededInfo> maxAssignmentExceededHandler) {
|
||||
super(targetRepository, afterCommit, actionRepository, actionStatusRepository,
|
||||
quotaManagement, multiAssignmentsConfig, confirmationFlowConfig, repositoryProperties, maxAssignmentExceededHandler);
|
||||
}
|
||||
@@ -128,8 +124,7 @@ class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Long> cancelActiveActions(final List<List<Long>> targetIds) {
|
||||
return Collections.emptySet();
|
||||
public void cancelActiveActions(final List<List<Long>> targetIds) {
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -137,14 +132,8 @@ class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
|
||||
// Not supported by offline case
|
||||
}
|
||||
|
||||
@Override
|
||||
void sendDeploymentEvents(final DistributionSetAssignmentResult assignmentResult) {
|
||||
// no need to send deployment events in the offline case
|
||||
}
|
||||
|
||||
@Override
|
||||
void sendDeploymentEvents(final List<DistributionSetAssignmentResult> assignmentResults) {
|
||||
// no need to send deployment events in the offline case
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -9,30 +9,25 @@
|
||||
*/
|
||||
package org.eclipse.hawkbit.repository.jpa.management;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.collections4.ListUtils;
|
||||
import org.apache.commons.lang3.function.TriConsumer;
|
||||
import org.eclipse.hawkbit.repository.QuotaManagement;
|
||||
import org.eclipse.hawkbit.repository.RepositoryProperties;
|
||||
import org.eclipse.hawkbit.repository.event.EventPublisherHolder;
|
||||
import org.eclipse.hawkbit.repository.event.remote.MultiActionAssignEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.MultiActionCancelEvent;
|
||||
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
|
||||
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
|
||||
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
|
||||
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
|
||||
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
|
||||
import org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor;
|
||||
import org.eclipse.hawkbit.repository.jpa.management.JpaDeploymentManagement.MaxAssignmentsExceededInfo;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaActionStatus;
|
||||
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
|
||||
@@ -59,7 +54,7 @@ class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
|
||||
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
|
||||
final QuotaManagement quotaManagement, final BooleanSupplier multiAssignmentsConfig,
|
||||
final BooleanSupplier confirmationFlowConfig, final RepositoryProperties repositoryProperties,
|
||||
final TriConsumer<Long, Long, AssignmentQuotaExceededException> maxAssignmentExceededHandler) {
|
||||
final Consumer<MaxAssignmentsExceededInfo> maxAssignmentExceededHandler) {
|
||||
super(targetRepository, afterCommit, actionRepository, actionStatusRepository,
|
||||
quotaManagement, multiAssignmentsConfig, confirmationFlowConfig, repositoryProperties, maxAssignmentExceededHandler);
|
||||
}
|
||||
@@ -129,7 +124,8 @@ class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAssignedDistributionSetAndTargetStatus(final JpaDistributionSet set, final List<List<Long>> targetIds, final String currentUser) {
|
||||
public void setAssignedDistributionSetAndTargetStatus(final JpaDistributionSet set, final List<List<Long>> targetIds,
|
||||
final String currentUser) {
|
||||
final long now = System.currentTimeMillis();
|
||||
targetIds.forEach(targetIdsChunk -> {
|
||||
if (targetRepository.count(AccessController.Operation.UPDATE,
|
||||
@@ -154,8 +150,8 @@ class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Long> cancelActiveActions(final List<List<Long>> targetIds) {
|
||||
return targetIds.stream().map(this::overrideObsoleteUpdateActions).flatMap(Collection::stream).collect(Collectors.toSet());
|
||||
public void cancelActiveActions(final List<List<Long>> targetIds) {
|
||||
targetIds.forEach(this::overrideObsoleteUpdateActions);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -163,15 +159,6 @@ class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
|
||||
targetIds.forEach(this::closeObsoleteUpdateActions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendDeploymentEvents(final DistributionSetAssignmentResult assignmentResult) {
|
||||
if (isMultiAssignmentsEnabled()) {
|
||||
sendDeploymentEvents(Collections.singletonList(assignmentResult));
|
||||
} else {
|
||||
sendDistributionSetAssignedEvent(assignmentResult);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendDeploymentEvents(final List<DistributionSetAssignmentResult> assignmentResults) {
|
||||
if (isMultiAssignmentsEnabled()) {
|
||||
@@ -190,7 +177,7 @@ class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
|
||||
}
|
||||
|
||||
void sendCancellationMessages(final List<JpaAction> actions, final String tenant) {
|
||||
if(isMultiAssignmentsEnabled()) {
|
||||
if (isMultiAssignmentsEnabled()) {
|
||||
sendMultiActionCancelEvent(tenant, Collections.unmodifiableList(actions));
|
||||
} else {
|
||||
actions.forEach(this::cancelAssignDistributionSetEvent);
|
||||
@@ -224,12 +211,10 @@ class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
|
||||
sendMultiActionAssignEvent(tenant, filteredActions);
|
||||
}
|
||||
|
||||
private DistributionSetAssignmentResult sendDistributionSetAssignedEvent(
|
||||
final DistributionSetAssignmentResult assignmentResult) {
|
||||
private void sendDistributionSetAssignedEvent(final DistributionSetAssignmentResult assignmentResult) {
|
||||
final List<Action> filteredActions = filterCancellations(assignmentResult.getAssignedEntity()).toList();
|
||||
final DistributionSet set = assignmentResult.getDistributionSet();
|
||||
sendTargetAssignDistributionSetEvent(set.getTenant(), set.getId(), filteredActions);
|
||||
return assignmentResult;
|
||||
}
|
||||
|
||||
private void sendTargetAssignDistributionSetEvent(final String tenant, final long distributionSetId,
|
||||
|
||||
@@ -320,15 +320,4 @@ public interface ActionRepository extends BaseEntityRepository<JpaAction> {
|
||||
@Transactional
|
||||
@Query("UPDATE JpaAction a SET a.externalRef = :externalRef WHERE a.id = :actionId")
|
||||
void updateExternalRef(@Param("actionId") Long actionId, @Param("externalRef") String externalRef);
|
||||
|
||||
/**
|
||||
* Deletes all actions with the given IDs.
|
||||
*
|
||||
* @param actionIDs the IDs of the actions to be deleted.
|
||||
*/
|
||||
@Modifying
|
||||
@Transactional
|
||||
// Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=349477
|
||||
@Query("DELETE FROM JpaAction a WHERE a.id IN ?1")
|
||||
void deleteByIdIn(Collection<Long> actionIDs);
|
||||
}
|
||||
@@ -43,11 +43,6 @@ public final class ActionSpecifications {
|
||||
cb.equal(root.get(JpaAction_.active), true));
|
||||
}
|
||||
|
||||
public static Specification<JpaAction> byIdIn(final List<Long> actionIds) {
|
||||
return ((root, query, cb) ->
|
||||
root.get(AbstractJpaBaseEntity_.id).in(actionIds));
|
||||
}
|
||||
|
||||
public static Specification<JpaAction> byTargetControllerId(final String controllerId) {
|
||||
return (root, query, cb) -> cb.equal(root.get(JpaAction_.target).get(JpaTarget_.controllerId), controllerId);
|
||||
}
|
||||
|
||||
@@ -53,12 +53,12 @@ public final class TargetSpecifications {
|
||||
/**
|
||||
* {@link Specification} for retrieving {@link Target}s including {@link TargetTag}s.
|
||||
*
|
||||
* @param controllerIDs to search for
|
||||
* @param controllerIds to search for
|
||||
* @return the {@link Target} {@link Specification}
|
||||
*/
|
||||
public static Specification<JpaTarget> byControllerIdWithTagsInJoin(final Collection<String> controllerIDs) {
|
||||
public static Specification<JpaTarget> byControllerIdWithTagsInJoin(final Collection<String> controllerIds) {
|
||||
return (targetRoot, query, cb) -> {
|
||||
final Predicate predicate = targetRoot.get(JpaTarget_.controllerId).in(controllerIDs);
|
||||
final Predicate predicate = targetRoot.get(JpaTarget_.controllerId).in(controllerIds);
|
||||
targetRoot.fetch(JpaTarget_.tags, JoinType.LEFT);
|
||||
query.distinct(true);
|
||||
return predicate;
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
package org.eclipse.hawkbit.repository.jpa.acm;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatNoException;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_DISTRIBUTION_SET;
|
||||
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_TARGET;
|
||||
@@ -56,17 +58,17 @@ class DeploymentManagementTest extends AbstractAccessControllerManagementTest {
|
||||
void verifyActionVisibility() {
|
||||
final String controllerId = target1Type1.getControllerId();
|
||||
verify(
|
||||
assignedId -> {
|
||||
actionId -> {
|
||||
assertThat(deploymentManagement.findActionsAll(UNPAGED)).isEmpty();
|
||||
assertThat(deploymentManagement.findAction(assignedId)).isEmpty();
|
||||
assertThatThrownBy(() -> deploymentManagement.findActionWithDetails(assignedId))
|
||||
assertThat(deploymentManagement.findAction(actionId)).isEmpty();
|
||||
assertThatThrownBy(() -> deploymentManagement.findActionWithDetails(actionId))
|
||||
.isInstanceOf(InsufficientPermissionException.class);
|
||||
assertThat(deploymentManagement.findActions("id==*", UNPAGED)).isEmpty();
|
||||
assertThatThrownBy(() -> deploymentManagement.findActionsByTarget(controllerId, UNPAGED))
|
||||
.isInstanceOf(EntityNotFoundException.class);
|
||||
assertThatThrownBy(() -> deploymentManagement.findActionsByTarget("id==*", controllerId, UNPAGED))
|
||||
.isInstanceOf(EntityNotFoundException.class);
|
||||
assertThatThrownBy(() -> deploymentManagement.findActionStatusByAction(assignedId, UNPAGED))
|
||||
assertThatThrownBy(() -> deploymentManagement.findActionStatusByAction(actionId, UNPAGED))
|
||||
.isInstanceOf(EntityNotFoundException.class);
|
||||
assertThatThrownBy(() -> deploymentManagement.findActiveActionsByTarget(controllerId, UNPAGED))
|
||||
.isInstanceOf(EntityNotFoundException.class);
|
||||
@@ -76,16 +78,16 @@ class DeploymentManagementTest extends AbstractAccessControllerManagementTest {
|
||||
assertThatThrownBy(() -> deploymentManagement.hasPendingCancellations(targetId)).isInstanceOf(
|
||||
EntityNotFoundException.class);
|
||||
},
|
||||
assignedId -> {
|
||||
actionId -> {
|
||||
assertThat(deploymentManagement.findActionsAll(UNPAGED)).hasSize(1).allMatch(this::isActionOfTarget1Type1);
|
||||
assertThat(deploymentManagement.findAction(assignedId)).hasValueSatisfying(this::assertActionOfTarget1Type1);
|
||||
assertThat(deploymentManagement.findActionWithDetails(assignedId)).hasValueSatisfying(this::assertActionOfTarget1Type1);
|
||||
assertThat(deploymentManagement.findAction(actionId)).hasValueSatisfying(this::assertActionOfTarget1Type1);
|
||||
assertThat(deploymentManagement.findActionWithDetails(actionId)).hasValueSatisfying(this::assertActionOfTarget1Type1);
|
||||
assertThat(deploymentManagement.findActions("id==*", UNPAGED)).hasSize(1).allMatch(this::isActionOfTarget1Type1);
|
||||
assertThat(deploymentManagement.findActionsByTarget(controllerId, UNPAGED)).hasSize(1)
|
||||
.allMatch(this::isActionOfTarget1Type1);
|
||||
assertThat(deploymentManagement.findActionsByTarget("id==*", controllerId, UNPAGED))
|
||||
.hasSize(1).allMatch(this::isActionOfTarget1Type1);
|
||||
assertThat(deploymentManagement.findActionStatusByAction(assignedId, UNPAGED))
|
||||
assertThat(deploymentManagement.findActionStatusByAction(actionId, UNPAGED))
|
||||
.hasSize(1).allMatch(actionStatus -> actionStatus.getStatus().equals(Action.Status.RUNNING));
|
||||
assertThat(deploymentManagement.findActiveActionsByTarget(controllerId, UNPAGED))
|
||||
.hasSize(1).allMatch(this::isActionOfTarget1Type1);
|
||||
@@ -96,24 +98,83 @@ class DeploymentManagementTest extends AbstractAccessControllerManagementTest {
|
||||
null);
|
||||
}
|
||||
|
||||
@Test
|
||||
void verifyDeleteActionById() {
|
||||
verify(
|
||||
null,
|
||||
actionId -> assertThatExceptionOfType(InsufficientPermissionException.class)
|
||||
.isThrownBy(() -> deploymentManagement.deleteAction(actionId)),
|
||||
actionId -> assertThatNoException().isThrownBy(() -> deploymentManagement.deleteAction(actionId)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void verifyDeleteActionsById() {
|
||||
verify(
|
||||
null,
|
||||
actionId -> {
|
||||
final List<Long> actionIds = List.of(actionId);
|
||||
assertThatExceptionOfType(InsufficientPermissionException.class)
|
||||
.isThrownBy(() -> deploymentManagement.deleteActionsByIds(actionIds));
|
||||
},
|
||||
actionId -> assertThatNoException().isThrownBy(() -> deploymentManagement.deleteActionsByIds(List.of(actionId))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void verifyDeleteActionByRSQL() {
|
||||
verify(
|
||||
null,
|
||||
actionId -> {
|
||||
assertThatNoException().isThrownBy(() -> deploymentManagement.deleteActionsByRsql("id==" + actionId));
|
||||
// no exception but the action shall not be deleted
|
||||
assertThat(deploymentManagement.findAction(actionId)).isPresent();
|
||||
},
|
||||
actionId -> {
|
||||
assertThatNoException().isThrownBy(() -> deploymentManagement.deleteActionsByRsql("id==" + actionId));
|
||||
// the action shall be deleted
|
||||
assertThat(deploymentManagement.findAction(actionId)).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void verifyDeleteTargetActionsById() {
|
||||
verify(
|
||||
null,
|
||||
actionId -> {
|
||||
final String controllerId = deploymentManagement.findAction(actionId)
|
||||
.map(Action::getTarget).map(Target::getControllerId).orElseThrow();
|
||||
final List<Long> actionIds = List.of(actionId, -1L);
|
||||
assertThatNoException().isThrownBy(() -> deploymentManagement.deleteTargetActionsByIds(controllerId, actionIds));
|
||||
// no exception but the action shall not be deleted
|
||||
assertThat(deploymentManagement.findAction(actionId)).isPresent();
|
||||
},
|
||||
actionId -> {
|
||||
final String controllerId = deploymentManagement.findAction(actionId)
|
||||
.map(Action::getTarget).map(Target::getControllerId).orElseThrow();
|
||||
assertThatNoException().isThrownBy(
|
||||
() -> deploymentManagement.deleteTargetActionsByIds(controllerId, List.of(actionId, -1L)));
|
||||
// the action shall be deleted
|
||||
assertThat(deploymentManagement.findAction(actionId)).isEmpty();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void verifyCancellation() {
|
||||
verify(
|
||||
assignedId -> assertThatThrownBy(() -> deploymentManagement.cancelAction(assignedId))
|
||||
actionId -> assertThatThrownBy(() -> deploymentManagement.cancelAction(actionId))
|
||||
.isInstanceOf(EntityNotFoundException.class),
|
||||
assignedId -> assertThatThrownBy(() -> deploymentManagement.cancelAction(assignedId))
|
||||
actionId -> assertThatThrownBy(() -> deploymentManagement.cancelAction(actionId))
|
||||
.isInstanceOf(InsufficientPermissionException.class),
|
||||
assignedId -> assertThat(deploymentManagement.cancelAction(assignedId).getId()).isEqualTo(assignedId));
|
||||
actionId -> assertThat(deploymentManagement.cancelAction(actionId).getId()).isEqualTo(actionId));
|
||||
}
|
||||
|
||||
@Test
|
||||
void verifyCancellationByDistributionSetId() {
|
||||
verify(
|
||||
assignedId -> {
|
||||
actionId -> {
|
||||
deploymentManagement.cancelActionsForDistributionSet(ActionCancellationType.FORCE, ds1Type1);
|
||||
assertThat(deploymentManagement.findAction(assignedId)).isEmpty();
|
||||
assertThat(deploymentManagement.findAction(actionId)).isEmpty();
|
||||
},
|
||||
assignedId -> assertThat(deploymentManagement.findAction(assignedId))
|
||||
actionId -> assertThat(deploymentManagement.findAction(actionId))
|
||||
.hasValueSatisfying(action -> assertThat(action.getStatus()).isEqualTo(Action.Status.RUNNING)),
|
||||
null);
|
||||
}
|
||||
@@ -121,37 +182,37 @@ class DeploymentManagementTest extends AbstractAccessControllerManagementTest {
|
||||
@Test
|
||||
void verifyForceActionIsNotAllowed() {
|
||||
verify(
|
||||
assignedId -> assertThatThrownBy(() -> deploymentManagement.forceTargetAction(assignedId))
|
||||
actionId -> assertThatThrownBy(() -> deploymentManagement.forceTargetAction(actionId))
|
||||
.isInstanceOf(EntityNotFoundException.class),
|
||||
assignedId -> assertThatThrownBy(() -> deploymentManagement.forceTargetAction(assignedId))
|
||||
actionId -> assertThatThrownBy(() -> deploymentManagement.forceTargetAction(actionId))
|
||||
.isInstanceOf(InsufficientPermissionException.class),
|
||||
assignedId -> assertThat(deploymentManagement.forceTargetAction(assignedId).getActionType())
|
||||
actionId -> assertThat(deploymentManagement.forceTargetAction(actionId).getActionType())
|
||||
.isEqualTo(Action.ActionType.FORCED));
|
||||
}
|
||||
|
||||
private void verify(final Consumer<Long> noRead, final Consumer<Long> noUpdate, final Consumer<Long> readAndUpdate) {
|
||||
final Long assignedId = systemSecurityContext.runAsSystem(() -> {
|
||||
final List<Action> assignedEntity = assignDistributionSet(ds1Type1.getId(), target1Type1.getControllerId()).getAssignedEntity();
|
||||
assertThat(assignedEntity).hasSize(1).allMatch(action -> action.getTarget().getId().equals(target1Type1.getId()));
|
||||
return assignedEntity.get(0);
|
||||
private void verify(final Consumer<Long> noRead, final Consumer<Long> readNoUpdate, final Consumer<Long> readAndUpdate) {
|
||||
final Long actionId = systemSecurityContext.runAsSystem(() -> {
|
||||
final List<Action> actions = assignDistributionSet(ds1Type1.getId(), target1Type1.getControllerId()).getAssignedEntity();
|
||||
assertThat(actions).hasSize(1).allMatch(action -> action.getTarget().getId().equals(target1Type1.getId()));
|
||||
return actions.get(0);
|
||||
}).getId();
|
||||
|
||||
if (noRead != null) {
|
||||
// no read permission
|
||||
runAs(withAuthorities(READ_TARGET + "/type.id==" + targetType2.getId(), UPDATE_TARGET + "/type.id==" + targetType2.getId()),
|
||||
() -> noRead.accept(assignedId));
|
||||
() -> noRead.accept(actionId));
|
||||
}
|
||||
|
||||
if (noUpdate != null) {
|
||||
if (readNoUpdate != null) {
|
||||
// read but no update permission
|
||||
runAs(withAuthorities(READ_TARGET + "/type.id==" + targetType1.getId(), UPDATE_TARGET + "/type.id==" + targetType2.getId()),
|
||||
() -> noUpdate.accept(assignedId));
|
||||
() -> readNoUpdate.accept(actionId));
|
||||
}
|
||||
|
||||
if (readAndUpdate != null) {
|
||||
// read and update permissions
|
||||
runAs(withAuthorities(READ_TARGET + "/type.id==" + targetType1.getId(), UPDATE_TARGET + "/type.id==" + targetType1.getId()),
|
||||
() -> readAndUpdate.accept(assignedId));
|
||||
() -> readAndUpdate.accept(actionId));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ package org.eclipse.hawkbit.repository.jpa.acm;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.eclipse.hawkbit.im.authentication.SpPermission.CREATE_PREFIX;
|
||||
import static org.eclipse.hawkbit.im.authentication.SpPermission.CREATE_TARGET;
|
||||
import static org.eclipse.hawkbit.im.authentication.SpPermission.DELETE_PREFIX;
|
||||
import static org.eclipse.hawkbit.im.authentication.SpPermission.DELETE_TARGET;
|
||||
import static org.eclipse.hawkbit.im.authentication.SpPermission.DISTRIBUTION_SET;
|
||||
@@ -23,6 +22,7 @@ import static org.eclipse.hawkbit.im.authentication.SpPermission.SOFTWARE_MODULE
|
||||
import static org.eclipse.hawkbit.im.authentication.SpPermission.SOFTWARE_MODULE_TYPE;
|
||||
import static org.eclipse.hawkbit.im.authentication.SpPermission.TARGET_TYPE;
|
||||
import static org.eclipse.hawkbit.im.authentication.SpPermission.UPDATE_PREFIX;
|
||||
import static org.eclipse.hawkbit.im.authentication.SpPermission.UPDATE_TARGET;
|
||||
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.runAs;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
@@ -92,17 +92,22 @@ class SystemExecutionTest extends AbstractAccessControllerManagementTest {
|
||||
() -> verifyAccessController(distributionSetTypeAccessController));
|
||||
runAs(withAuthorities(READ_TARGET, DELETE_TARGET + "/type.id==1"),
|
||||
() -> verifyAccessController(targetAccessController));
|
||||
runAs(withAuthorities(UPDATE_PREFIX + TARGET_TYPE + "/id==1"), () -> verifyAccessController(targetTypeAccessController));
|
||||
runAs(withAuthorities(CREATE_TARGET + "/type.id==1"), () -> verifyAccessController(actionAccessController));
|
||||
runAs(withAuthorities(CREATE_PREFIX + TARGET_TYPE + "/id==1"), () -> verifyAccessController(targetTypeAccessController));
|
||||
// Action Access Controller maps CREATE/UPDATE/DELETE to UPDATE - so only UPDATE (or READ scope) is relevant
|
||||
// and update will be called for every of the mapped - so 3 times
|
||||
runAs(withAuthorities(UPDATE_TARGET + "/type.id==1"), () -> verifyAccessController(actionAccessController, 3));
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private void verifyAccessController(final AccessController<?> accessController) {
|
||||
verifyAccessController(accessController, 1);
|
||||
}
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private void verifyAccessController(final AccessController<?> accessController, final int times) {
|
||||
final Specification mock = mock(Specification.class);
|
||||
for (final Operation operation : Operation.values()) {
|
||||
accessController.appendAccessRules(operation, mock);
|
||||
}
|
||||
verify(mock, times(1)).and(any()); // once for every access controller is scoped only
|
||||
verify(mock, times(times)).and(any()); // once for every access controller is scoped only
|
||||
|
||||
final Specification mockAsSystem = mock(Specification.class);
|
||||
for (Operation operation : Operation.values()) {
|
||||
|
||||
Reference in New Issue
Block a user