diff --git a/hawkbit-mgmt/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/softwaremoduletype/MgmtSoftwareModuleType.java b/hawkbit-mgmt/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/softwaremoduletype/MgmtSoftwareModuleType.java index dcd85944f..441a4eb44 100644 --- a/hawkbit-mgmt/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/softwaremoduletype/MgmtSoftwareModuleType.java +++ b/hawkbit-mgmt/hawkbit-mgmt-api/src/main/java/org/eclipse/hawkbit/mgmt/json/model/softwaremoduletype/MgmtSoftwareModuleType.java @@ -32,7 +32,7 @@ import org.eclipse.hawkbit.mgmt.json.model.MgmtTypeEntity; @JsonIgnoreProperties(ignoreUnknown = true) @Schema(example = """ { - "createdBy" : "system", + "createdBy" : "admin", "createdAt" : 1682408579390, "lastModifiedBy" : "bumlux", "lastModifiedAt" : 1682408579394, diff --git a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java index 1882e5336..56ce40226 100644 --- a/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java +++ b/hawkbit-repository/hawkbit-repository-api/src/main/java/org/eclipse/hawkbit/repository/TargetManagement.java @@ -54,11 +54,14 @@ import org.springframework.util.ObjectUtils; /** * Management service for {@link Target}s. */ +@SuppressWarnings("java:S1192") // java:S1192 nothing meaningful to add + would be interface constant public interface TargetManagement extends RepositoryManagement { String HAS_READ_TARGET_AND_READ_ROLLOUT = HAS_READ_REPOSITORY + " and hasAuthority('READ_" + SpPermission.ROLLOUT + "')"; + String HAS_UPDATE_TARGET_AND_READ_ROLLOUT = HAS_UPDATE_REPOSITORY + " and hasAuthority('READ_" + SpPermission.ROLLOUT + "')"; String HAS_READ_TARGET_AND_READ_DISTRIBUTION_SET = HAS_READ_REPOSITORY + " and hasAuthority('READ_" + SpPermission.DISTRIBUTION_SET + "')"; + String HAS_UPDATE_TARGET_AND_READ_DISTRIBUTION_SET = HAS_UPDATE_REPOSITORY + " and hasAuthority('READ_" + SpPermission.DISTRIBUTION_SET + "')"; String DETAILS_AUTO_CONFIRMATION_STATUS = "autoConfirmationStatus"; String DETAILS_TAGS = "tags"; @@ -87,7 +90,7 @@ public interface TargetManagement * @param targetFilterQuery to execute * @return true if it matches */ - @PreAuthorize(HAS_READ_TARGET_AND_READ_DISTRIBUTION_SET) + @PreAuthorize(HAS_UPDATE_TARGET_AND_READ_DISTRIBUTION_SET) boolean isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable( @NotNull String controllerId, long distributionSetId, @NotNull String targetFilterQuery); @@ -137,7 +140,7 @@ public interface TargetManagement * @return a page of the found {@link Target}s * @throws EntityNotFoundException if distribution set with given ID does not exist */ - @PreAuthorize(HAS_READ_TARGET_AND_READ_DISTRIBUTION_SET) + @PreAuthorize(HAS_UPDATE_TARGET_AND_READ_DISTRIBUTION_SET) Slice findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable( long distributionSetId, @NotNull String rsql, @NotNull Pageable pageable); @@ -151,7 +154,7 @@ public interface TargetManagement * @param pageable the pageable to enhance the query for paging and sorting * @return a page of the found {@link Target}s */ - @PreAuthorize(HAS_READ_TARGET_AND_READ_ROLLOUT) + @PreAuthorize(HAS_UPDATE_TARGET_AND_READ_ROLLOUT) Slice findByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable( @NotEmpty Collection groups, @NotNull String rsql, @NotNull DistributionSetType distributionSetType, @NotNull Pageable pageable); @@ -169,7 +172,7 @@ public interface TargetManagement Slice findByFailedRolloutAndNotInRolloutGroups( @NotNull String rolloutId, @NotEmpty Collection groups, @NotNull Pageable pageable); - @PreAuthorize(HAS_READ_TARGET_AND_READ_ROLLOUT) + @PreAuthorize(HAS_UPDATE_TARGET_AND_READ_ROLLOUT) Slice findByRsqlAndNoOverridingActionsAndNotInRolloutAndCompatibleAndUpdatable( final long rolloutId, @NotNull String rsql, @NotNull DistributionSetType distributionSetType, @NotNull Pageable pageable); @@ -293,7 +296,7 @@ public interface TargetManagement * @return the count of found {@link Target}s * @throws EntityNotFoundException if distribution set with given ID does not exist */ - @PreAuthorize(HAS_READ_TARGET_AND_READ_DISTRIBUTION_SET) + @PreAuthorize(HAS_UPDATE_TARGET_AND_READ_DISTRIBUTION_SET) long countByRsqlAndNonDsAndCompatibleAndUpdatable(long distributionSetId, @NotNull String rsql); /** @@ -305,7 +308,7 @@ public interface TargetManagement * @param distributionSetType type of the {@link DistributionSet} the targets must be compatible with * @return count of the found {@link Target}s */ - @PreAuthorize(HAS_READ_TARGET_AND_READ_ROLLOUT) + @PreAuthorize(HAS_UPDATE_TARGET_AND_READ_ROLLOUT) long countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable( @NotNull String rsql, @NotEmpty Collection groups, @NotNull DistributionSetType distributionSetType); diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/acm/DefaultAccessController.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/acm/DefaultAccessController.java index 41adda8de..6fbe7a4e6 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/acm/DefaultAccessController.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/acm/DefaultAccessController.java @@ -9,8 +9,6 @@ */ package org.eclipse.hawkbit.repository.jpa.acm; -import static org.eclipse.hawkbit.security.SecurityContextTenantAware.SYSTEM_USER; - import java.util.ArrayList; import java.util.EnumMap; import java.util.List; @@ -24,6 +22,7 @@ import org.eclipse.hawkbit.ContextAware; import org.eclipse.hawkbit.repository.QueryField; import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException; import org.eclipse.hawkbit.repository.jpa.ql.QLSupport; +import org.eclipse.hawkbit.security.SystemSecurityContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.domain.Specification; import org.springframework.security.core.GrantedAuthority; @@ -58,8 +57,8 @@ public class DefaultAccessController & QueryField, T> implemen @Override public Optional> getAccessRules(final Operation operation) { - if (contextAware.getCurrentTenant() != null && SYSTEM_USER.equals(contextAware.getCurrentUsername())) { - // as tenant, no restrictions + if (SystemSecurityContext.isCurrentThreadSystemCode()) { + // system code - no restrictions. this runs with SYSTEM_ROLE, so no restrictions apply anyway - not scopes, but this way should be faster return Optional.empty(); } @@ -73,8 +72,8 @@ public class DefaultAccessController & QueryField, T> implemen @Override public void assertOperationAllowed(final Operation operation, final T entity) throws InsufficientPermissionException { - if (contextAware.getCurrentTenant() != null && SYSTEM_USER.equals(contextAware.getCurrentUsername())) { - // as tenant, no restrictions + if (SystemSecurityContext.isCurrentThreadSystemCode()) { + // system code - no restrictions. this runs with SYSTEM_ROLE, so no restrictions apply anyway - not scopes, but this way should be faster return; } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetInvalidationManagement.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetInvalidationManagement.java index 18cb80f1b..fe96653cc 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetInvalidationManagement.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/management/JpaDistributionSetInvalidationManagement.java @@ -134,7 +134,6 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet systemSecurityContext.runAsSystem(() -> { log.debug("Cancel auto assignments after ds invalidation. ID: {}", setId); targetFilterQueryManagement.cancelAutoAssignmentForDistributionSet(setId); - return null; }); } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/RolloutScheduler.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/RolloutScheduler.java index f9f7af268..f02ed36ee 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/RolloutScheduler.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/RolloutScheduler.java @@ -73,7 +73,6 @@ public class RolloutScheduler { handleAllAsync(tenant); } }); - return null; }); meterRegistry diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/PauseRolloutGroupAction.java b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/PauseRolloutGroupAction.java index 653f226cb..9d73e4c17 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/PauseRolloutGroupAction.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/main/java/org/eclipse/hawkbit/repository/jpa/rollout/condition/PauseRolloutGroupAction.java @@ -61,7 +61,6 @@ public class PauseRolloutGroupAction implements RolloutGroupActionEvaluator systemSecurityContext.runAsSystem(() -> { - rolloutHandler.handleAll(); - return null; - })); + verify(() -> systemSecurityContext.runAsSystem(rolloutHandler::handleAll)); } private void verify(final Runnable run) { diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/SystemExecutionTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/SystemExecutionTest.java index d33c106d9..7e55007d5 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/SystemExecutionTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/SystemExecutionTest.java @@ -106,10 +106,7 @@ class SystemExecutionTest extends AbstractAccessControllerManagementTest { final Specification mockAsSystem = mock(Specification.class); for (Operation operation : Operation.values()) { - systemSecurityContext.runAsSystem(() -> { - accessController.appendAccessRules(operation, mockAsSystem); - return null; - }); + systemSecurityContext.runAsSystem(() -> accessController.appendAccessRules(operation, mockAsSystem)); } verifyNoInteractions(mockAsSystem); } diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/TargetManagementTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/TargetManagementTest.java index 458ed1756..3e17047d1 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/TargetManagementTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/acm/TargetManagementTest.java @@ -21,7 +21,6 @@ import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_ROLLOUT; import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_TARGET; import static org.eclipse.hawkbit.im.authentication.SpPermission.UPDATE_TARGET; import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.runAs; -import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.withUser; import java.util.Arrays; import java.util.List; @@ -37,12 +36,9 @@ import org.eclipse.hawkbit.repository.model.Action; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.NamedEntity; import org.eclipse.hawkbit.repository.model.Rollout; -import org.eclipse.hawkbit.repository.model.RolloutGroup; import org.eclipse.hawkbit.repository.model.Target; import org.eclipse.hawkbit.repository.model.TargetTag; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import org.springframework.data.domain.Pageable; class TargetManagementTest extends AbstractAccessControllerManagementTest { @@ -130,57 +126,69 @@ class TargetManagementTest extends AbstractAccessControllerManagementTest { }); } - @Disabled @Test - void verifyReadRolloutRelated() { - assertThat(assignDistributionSet(ds2Type2, List.of(target1Type1, target2Type2, target3Type2)).getAssigned()).isEqualTo(3); + void verifyReadCompatibleRelated() { +// assertThat(assignDistributionSet(ds2Type2, List.of(target1Type1, target2Type2, target3Type2)).getAssigned()).isEqualTo(3); prepareFinishedUpdates(ds2Type2, target1Type1, target2Type2); final Target target1Type1 = targetManagement.get(super.target1Type1.getId()); runAs(withAuthorities( - READ_TARGET + "/type.id==" + targetType1.getId(), - READ_DISTRIBUTION_SET, - CREATE_ROLLOUT, READ_ROLLOUT, HANDLE_ROLLOUT), () -> { + READ_TARGET + "/type.id==" + targetType2.getId(), // we want to have 2 updatable targets + READ_DISTRIBUTION_SET), () -> { assertThat(targetManagement.findByInstalledDistributionSet(ds2Type2.getId(), UNPAGED)) - .extracting(Identifiable::getId).containsExactly(target1Type1.getId()); + .extracting(Identifiable::getId).containsExactly(target2Type2.getId()); assertThat(targetManagement.findByInstalledDistributionSetAndRsql(ds2Type2.getId(), "id==*", UNPAGED)) - .extracting(Identifiable::getId).containsExactly(target1Type1.getId()); - - assertThat(targetManagement.findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable( - ds2Type2.getId(), "id==*", UNPAGED)) - .hasSize(1) - .extracting(Identifiable::getId).containsOnly(target1Type1.getId()); + .extracting(Identifiable::getId).containsExactly(target2Type2.getId()); + }); + runAs(withAuthorities( + READ_TARGET, UPDATE_TARGET + "/type.id==" + targetType2.getId(), // we want to have 2 updatable targets + READ_DISTRIBUTION_SET), () -> { + assertThat(targetManagement.findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(ds2Type2.getId(), "id==*", UNPAGED)) + .extracting(Identifiable::getId).containsExactly(target3Type2.getId()); assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable( - target1Type1.getControllerId(), ds2Type2.getId(), "id==*")).isTrue(); + target1Type1.getControllerId(), ds2Type2.getId(), "id==*")).isFalse(); assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable( target2Type2.getControllerId(), ds2Type2.getId(), "id==*")).isFalse(); + assertThat(targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable( + target3Type2.getControllerId(), ds2Type2.getId(), "id==*")).isTrue(); + }); + } + @Test + void verifyReadRolloutRelated() { + final Target target1Type1 = targetManagement.get(super.target1Type1.getId()); + runAs(withAuthorities( + READ_TARGET, UPDATE_TARGET + "/type.id==" + targetType1.getId(), + READ_DISTRIBUTION_SET, + CREATE_ROLLOUT, READ_ROLLOUT, HANDLE_ROLLOUT), () -> { assertThat(targetManagement.findByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable( - List.of(1L), "id==*", ds2Type2.getType(), UNPAGED)) - .hasSize(1) - .extracting(Identifiable::getId).containsOnly(target1Type1.getId()); - assertThat(targetManagement.countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable("id==*", List.of(1L), - ds2Type2.getType())).isEqualTo(1); - - final Rollout rollout = testdataFactory.createRolloutByVariables( - "testRollout", "testDescription", 3, "id==*", ds2Type2, "50", "5"); - - final List foundTargetIds = rolloutGroupManagement.findByRollout(rollout.getId(), UNPAGED).getContent().stream() - .flatMap(rolloutGroup -> targetManagement.findByInRolloutGroupWithoutAction(rolloutGroup.getId(), UNPAGED) - .getContent().stream()).map(Identifiable::getId).toList(); - assertThat(foundTargetIds).hasSize(1).contains(target1Type1.getId()); - - assertThat(targetManagement.findByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable( - List.of(1L), "id==*", ds2Type2.getType(), UNPAGED)) - .hasSize(1) - .extracting(Identifiable::getId).containsOnly(target1Type1.getId()); + List.of(1L), "id==*", ds2Type2.getType(), UNPAGED).stream().toList()) + .containsExactly(target1Type1); assertThat(targetManagement.countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable("id==*", List.of(1L), ds2Type2.getType())) .isEqualTo(1); + + final Rollout rollout = testdataFactory.createRolloutByVariables("testRollout", "testDescription", 3, "id==*", ds2Type2, "50", "5"); + final List groups = rolloutGroupManagement.findByRollout(rollout.getId(), UNPAGED).getContent().stream(). + map(Identifiable::getId).toList(); + assertThat(groups.stream().flatMap( + group -> targetManagement.findByInRolloutGroupWithoutAction(group, UNPAGED).get()).toList()) + .containsExactly(target1Type1); + assertThat(groups.stream().flatMap( + group -> rolloutGroupManagement.findTargetsOfRolloutGroup(group, UNPAGED).get()).toList()) + .containsExactly(target1Type1); + + assertThat(targetManagement.findByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable(groups, "id==*", ds2Type2.getType(), UNPAGED)) + .isEmpty(); + assertThat(targetManagement.countByRsqlAndNotInRolloutGroupsAndCompatibleAndUpdatable("id==*", groups, ds2Type2.getType())) + .isZero(); + + // as system in context - doesn't apply scopes final Rollout runAsSystem = systemSecurityContext.runAsSystem( - () -> testdataFactory.createRolloutByVariables("testRollout", "testDescription", 3, "id==*", ds2Type2, "50", "5")); + () -> testdataFactory.createRolloutByVariables( + "testRolloutAsSystem", "testDescriptionAsSystem", 3, "id==*", ds2Type2, "50", "5")); assertThat(rolloutGroupManagement.findByRollout(runAsSystem.getId(), UNPAGED).getContent().stream() - .flatMap(rolloutGroup -> targetManagement.findByInRolloutGroupWithoutAction(rolloutGroup.getId(), UNPAGED) - .getContent().stream()).map(Identifiable::getId).toList()) + .flatMap( + group -> targetManagement.findByInRolloutGroupWithoutAction(group.getId(), UNPAGED).getContent().stream()).toList()) .hasSize(3); }); } @@ -288,44 +296,4 @@ class TargetManagementTest extends AbstractAccessControllerManagementTest { Action.ActionStatusCreate.builder().actionId(action.getId()).status(Action.Status.FINISHED).build()); }); } - - /** - * Verifies only manageable targets are part of the rollout - */ - @Test - void verifyRolloutTargetScope() { - final DistributionSet ds = testdataFactory.createDistributionSet("myDs"); - distributionSetManagement.lock(ds); - - final String[] updateTargetControllerIds = { "update1", "update2", "update3" }; - final List updateTargets = testdataFactory.createTargets(updateTargetControllerIds); - final String[] readTargetControllerIds = { "read1", "read2", "read3", "read4" }; - final List readTargets = testdataFactory.createTargets(readTargetControllerIds); - final List hiddenTargets = testdataFactory.createTargets("hidden1", "hidden2", "hidden3", "hidden4", "hidden5"); - - runAs(withUser("user", - READ_DISTRIBUTION_SET, - READ_TARGET + "/controllerId=in=(" + String.join(", ", List.of(updateTargetControllerIds)) + ")" + - " or controllerId=in=(" + String.join(", ", List.of(readTargetControllerIds)) + ")", - UPDATE_TARGET + "/controllerId=in=(" + String.join(", ", List.of(updateTargetControllerIds)) + ")", - CREATE_ROLLOUT, READ_ROLLOUT), () -> { - final Rollout rollout = testdataFactory.createRolloutByVariables( - "testRollout", "description", updateTargets.size(), "id==*", ds, "50", "5"); - assertThat(rollout.getTotalTargets()).isEqualTo(updateTargets.size()); - - final List content = rolloutGroupManagement.findByRollout(rollout.getId(), Pageable.unpaged()).getContent(); - assertThat(content).hasSize(updateTargets.size()); - - final List rolloutTargets = content.stream().flatMap( - group -> rolloutGroupManagement.findTargetsOfRolloutGroup(group.getId(), Pageable.unpaged()).get()) - .toList(); - - assertThat(rolloutTargets).hasSize(updateTargets.size()).allMatch( - target -> updateTargets.stream().anyMatch(readTarget -> readTarget.getId().equals(target.getId()))) - .noneMatch(target -> readTargets.stream() - .anyMatch(readTarget -> readTarget.getId().equals(target.getId()))) - .noneMatch(target -> hiddenTargets.stream() - .anyMatch(readTarget -> readTarget.getId().equals(target.getId()))); - }); - } } \ No newline at end of file diff --git a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/VirtualPropertyResolverTest.java b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/VirtualPropertyResolverTest.java index efb06d09e..0cf7f5ee1 100644 --- a/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/VirtualPropertyResolverTest.java +++ b/hawkbit-repository/hawkbit-repository-jpa/src/test/java/org/eclipse/hawkbit/repository/jpa/rsql/VirtualPropertyResolverTest.java @@ -91,7 +91,7 @@ class VirtualPropertyResolverTest { @ParameterizedTest @ValueSource(strings = { "${NOW_TS}", "${OVERDUE_TS}", "${overdue_ts}" }) void resolveNowTimestampPlaceholder(final String placeholder) { - when(securityContext.runAsSystem(Mockito.any())).thenAnswer(a -> ((Callable) a.getArgument(0)).call()); + when(securityContext.runAsSystem(Mockito.any(Callable.class))).thenAnswer(a -> ((Callable) a.getArgument(0)).call()); final String testString = "lhs=lt=" + placeholder; final String resolvedPlaceholders = substitutor.replace(testString); diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/CleanupTestExecutionListener.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/CleanupTestExecutionListener.java index 812669ef2..3df7f0f44 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/CleanupTestExecutionListener.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/CleanupTestExecutionListener.java @@ -53,10 +53,7 @@ public class CleanupTestExecutionListener extends AbstractTestExecutionListener final List tenants = systemSecurityContext.runAsSystem(() -> systemManagement.findTenants(PAGE).getContent()); tenants.forEach(tenant -> { try { - systemSecurityContext.runAsSystem(() -> { - systemManagement.deleteTenant(tenant); - return null; - }); + systemSecurityContext.runAsSystem(() -> systemManagement.deleteTenant(tenant)); } catch (final Exception e) { log.error("Error while delete tenant", e); } diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestdataFactory.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestdataFactory.java index bb973b0bb..ba6de6396 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestdataFactory.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/TestdataFactory.java @@ -10,9 +10,6 @@ package org.eclipse.hawkbit.repository.test.util; import static org.assertj.core.api.Assertions.assertThat; -import static org.eclipse.hawkbit.im.authentication.SpRole.SYSTEM_ROLE; -import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.runAs; -import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.withUserAndTenant; import java.io.ByteArrayInputStream; import java.io.InputStream; @@ -57,7 +54,6 @@ import org.eclipse.hawkbit.repository.model.ActionStatus; import org.eclipse.hawkbit.repository.model.Artifact; import org.eclipse.hawkbit.repository.model.ArtifactUpload; import org.eclipse.hawkbit.repository.model.BaseEntity; -import org.eclipse.hawkbit.repository.model.DeploymentRequest; import org.eclipse.hawkbit.repository.model.DistributionSet; import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation; import org.eclipse.hawkbit.repository.model.DistributionSetTag; @@ -78,6 +74,7 @@ import org.eclipse.hawkbit.repository.model.TargetFilterQuery; import org.eclipse.hawkbit.repository.model.TargetTag; import org.eclipse.hawkbit.repository.model.TargetType; import org.eclipse.hawkbit.repository.model.TargetUpdateStatus; +import org.eclipse.hawkbit.security.SystemSecurityContext; import org.eclipse.hawkbit.tenancy.TenantAware; import org.springframework.context.annotation.Profile; import org.springframework.data.domain.PageRequest; @@ -162,6 +159,7 @@ public class TestdataFactory { private final RolloutHandler rolloutHandler; private final QuotaManagement quotaManagement; private final TenantAware tenantAware; + private final SystemSecurityContext systemSecurityContext; public TestdataFactory( final ControllerManagement controllerManagement, final ArtifactManagement artifactManagement, @@ -177,7 +175,8 @@ public class TestdataFactory { final TargetTagManagement targetTagManagement, final DeploymentManagement deploymentManagement, final RolloutManagement rolloutManagement, final RolloutHandler rolloutHandler, - final QuotaManagement quotaManagement, final TenantAware tenantAware) { + final QuotaManagement quotaManagement, + final TenantAware tenantAware, final SystemSecurityContext systemSecurityContext) { this.controllerManagement = controllerManagement; this.softwareModuleManagement = softwareModuleManagement; this.softwareModuleTypeManagement = softwareModuleTypeManagement; @@ -195,6 +194,7 @@ public class TestdataFactory { this.rolloutHandler = rolloutHandler; this.quotaManagement = quotaManagement; this.tenantAware = tenantAware; + this.systemSecurityContext = systemSecurityContext; } public static String randomString(final int len) { @@ -205,14 +205,6 @@ public class TestdataFactory { return randomString(len).getBytes(); } - public Action performAssignment(final DistributionSet distributionSet) { - final Target target = createTarget(randomString(5)); - final DeploymentRequest deploymentRequest = new DeploymentRequest( - target.getControllerId(), distributionSet.getId(), ActionType.FORCED, 0, null, null, null, null, false); - deploymentManagement.assignDistributionSets(Collections.singletonList(deploymentRequest)); - return deploymentManagement.findActionsByTarget(target.getControllerId(), Pageable.unpaged()).getContent().get(0); - } - /** * Creates {@link DistributionSet} in repository including three * {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} , @@ -1294,7 +1286,7 @@ public class TestdataFactory { if (tenant == null) { throw new IllegalStateException("Tenant is null"); } - runAs(withUserAndTenant("system", tenant, false, false, false, SYSTEM_ROLE), rolloutHandler::handleAll); + systemSecurityContext.runAsSystem(rolloutHandler::handleAll); } private Rollout reloadRollout(final Rollout rollout) { diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/audit/AuditContextProvider.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/audit/AuditContextProvider.java index 9637926a0..160d01a00 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/audit/AuditContextProvider.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/audit/AuditContextProvider.java @@ -12,6 +12,7 @@ package org.eclipse.hawkbit.audit; import java.util.Optional; import lombok.NoArgsConstructor; +import org.eclipse.hawkbit.security.SecurityContextTenantAware; import org.eclipse.hawkbit.tenancy.TenantAware; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.AuditorAware; @@ -42,7 +43,7 @@ public class AuditContextProvider { public AuditContext getAuditContext() { return new AuditContext( Optional.ofNullable(resolver.resolveTenant()).orElse("n/a"), - Optional.ofNullable(auditorAware).flatMap(AuditorAware::getCurrentAuditor).orElse("system")); + Optional.ofNullable(auditorAware).flatMap(AuditorAware::getCurrentAuditor).orElse(SecurityContextTenantAware.SYSTEM_USER)); } public record AuditContext(String tenant, String username) {} diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/MdcHandler.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/MdcHandler.java index 2daced2d9..aca000d0e 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/MdcHandler.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/MdcHandler.java @@ -84,7 +84,7 @@ public class MdcHandler { final String user = springSecurityAuditorAware .getCurrentAuditor() - .filter(username -> !username.equals("system")) // null and system are the same - system user + .filter(username -> !username.equals(SecurityContextTenantAware.SYSTEM_USER)) // null and system are the same - system user .orElse(null); return callWithTenantAndUser0(callable, tenant, user); diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java index e32c51f1a..2bcc32003 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SecurityContextTenantAware.java @@ -40,6 +40,7 @@ import org.springframework.security.oauth2.core.oidc.user.OidcUser; */ public class SecurityContextTenantAware implements ContextAware { + // Note! no system user shall be used as a regular user! public static final String SYSTEM_USER = "system"; private static final Collection SYSTEM_AUTHORITIES = List.of(new SimpleGrantedAuthority(SpRole.SYSTEM_ROLE)); @@ -181,7 +182,8 @@ public class SecurityContextTenantAware implements ContextAware { private final TenantAwareUser principal; private final TenantAwareAuthenticationDetails tenantAwareAuthenticationDetails; - private AuthenticationDelegate(final Authentication delegate, final String tenant, final String username, + private AuthenticationDelegate( + final Authentication delegate, final String tenant, final String username, final Collection authorities) { this.delegate = delegate; principal = new TenantAwareUser(username, username, authorities, tenant); diff --git a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java index cb736d04e..92a4c2f4c 100644 --- a/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java +++ b/hawkbit-security-core/src/main/java/org/eclipse/hawkbit/security/SystemSecurityContext.java @@ -54,6 +54,22 @@ public class SystemSecurityContext { this.roleHierarchy = roleHierarchy; } + /** + * Runs a given {@link Runnable} within a system security context, which is permitted to call secured system code. Often the system needs + * to call secured methods by its own without relying on the current security context e.g. if the current security context does not contain + * the necessary permission it's necessary to execute code as system code to execute necessary methods and functionality.
+ * The security context will be switched to the system code and back after the callable is called.
+ * The system code is executed for a current tenant by using the {@link TenantAware#getCurrentTenant()}. + * + * @param runnable the runnable to call within the system security context + */ + public void runAsSystem(final Runnable runnable) { + runAsSystemAsTenant(() -> { + runnable.run(); + return null; + }, tenantAware.getCurrentTenant()); + } + /** * Runs a given {@link Callable} within a system security context, which is permitted to call secured system code. Often the system needs * to call secured methods by its own without relying on the current security context e.g. if the current security context does not contain @@ -120,7 +136,7 @@ public class SystemSecurityContext { /** * @return {@code true} if the current running code is running as system code block. */ - public boolean isCurrentThreadSystemCode() { + public static boolean isCurrentThreadSystemCode() { return SecurityContextHolder.getContext().getAuthentication() instanceof SystemCodeAuthentication; }