Refactor hawkbit core and security (#2833)

* Refactor hawkbit core and security

* improve access to the base core features - static
* thus easiear access
* and less boilerplate passing of instances

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>

* Refactor context classes

* make JSON context serialization default

* AccessContext

* Split hawkbit-security-core to other modules and remove it

---------

Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2025-11-27 13:07:49 +02:00
committed by GitHub
parent 58dbc32a80
commit f6f62db0ad
274 changed files with 2534 additions and 4458 deletions

View File

@@ -34,7 +34,7 @@
</dependency>
<dependency>
<groupId>org.eclipse.hawkbit</groupId>
<artifactId>hawkbit-security-core</artifactId>
<artifactId>hawkbit-core</artifactId>
<version>${project.version}</version>
</dependency>

View File

@@ -17,8 +17,8 @@ import jakarta.validation.constraints.NotNull;
import org.eclipse.hawkbit.artifact.exception.ArtifactUploadFailedException;
import org.eclipse.hawkbit.artifact.model.ArtifactStream;
import org.eclipse.hawkbit.artifact.model.StoredArtifactInfo;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.auth.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.InvalidMd5HashException;

View File

@@ -15,11 +15,10 @@ import java.util.Optional;
import jakarta.validation.constraints.NotEmpty;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.auth.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.AutoConfirmationStatus;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.security.access.prepost.PreAuthorize;
/**
@@ -40,7 +39,7 @@ public interface ConfirmationManagement extends PermissionSupport {
* Activate auto confirmation for a given controller ID. In case auto confirmation is active already, this method will fail with an exception.
*
* @param controllerId to activate the feature for
* @param initiator who initiated this operation. If 'null' we will take the current user from {@link TenantAware#getCurrentUsername()}
* @param initiator who initiated this operation.
* @param remark optional field to set a remark
* @return the persisted {@link AutoConfirmationStatus}
*/

View File

@@ -20,7 +20,7 @@ import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.auth.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;

View File

@@ -9,13 +9,11 @@
*/
package org.eclipse.hawkbit.repository;
import static org.eclipse.hawkbit.im.authentication.SpPermission.CREATE_ROLLOUT;
import static org.eclipse.hawkbit.im.authentication.SpPermission.DISTRIBUTION_SET;
import static org.eclipse.hawkbit.im.authentication.SpPermission.HANDLE_ROLLOUT;
import static org.eclipse.hawkbit.im.authentication.SpPermission.ROLLOUT;
import static org.eclipse.hawkbit.im.authentication.SpPermission.UPDATE_ROLLOUT;
import static org.eclipse.hawkbit.im.authentication.SpringEvalExpressions.HAS_READ_REPOSITORY;
import static org.eclipse.hawkbit.im.authentication.SpringEvalExpressions.HAS_UPDATE_REPOSITORY;
import static org.eclipse.hawkbit.auth.SpPermission.CREATE_ROLLOUT;
import static org.eclipse.hawkbit.auth.SpPermission.DISTRIBUTION_SET;
import static org.eclipse.hawkbit.auth.SpPermission.UPDATE_ROLLOUT;
import static org.eclipse.hawkbit.auth.SpringEvalExpressions.HAS_READ_REPOSITORY;
import static org.eclipse.hawkbit.auth.SpringEvalExpressions.HAS_UPDATE_REPOSITORY;
import java.util.Collection;
import java.util.List;
@@ -27,8 +25,8 @@ import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.auth.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
@@ -51,6 +49,7 @@ import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.lang.Nullable;
import org.springframework.security.access.prepost.PreAuthorize;
/**
@@ -70,23 +69,6 @@ public interface DeploymentManagement extends PermissionSupport {
* Assigns {@link DistributionSet}s to {@link Target}s according to the {@link DeploymentRequest}.
*
* @param deploymentRequests information about all target-ds-assignments that shall be made
* @return the list of assignment results
* @throws IncompleteDistributionSetException if mandatory {@link SoftwareModuleType} are not assigned as
* defined by the {@link DistributionSetType}.
* @throws EntityNotFoundException if either provided {@link DistributionSet} or {@link Target}s do not exist
* @throws AssignmentQuotaExceededException if the maximum number of targets the distribution set can be
* assigned to at once is exceeded
* @throws MultiAssignmentIsNotEnabledException if the request results in multiple assignments to the same
* target and multi-assignment is disabled
*/
@PreAuthorize(HAS_UPDATE_TARGET_AND_READ_DISTRIBUTION_SET)
List<DistributionSetAssignmentResult> assignDistributionSets(@Valid @NotEmpty List<DeploymentRequest> deploymentRequests);
/**
* Assigns {@link DistributionSet}s to {@link Target}s according to the {@link DeploymentRequest}.
*
* @param initiatedBy the username of the user who initiated the assignment
* @param deploymentRequests information about all target-ds-assignments that shall be made
* @param actionMessage an optional message for the action status
* @return the list of assignment results
* @throws IncompleteDistributionSetException if mandatory {@link SoftwareModuleType} are not assigned as
@@ -99,7 +81,7 @@ public interface DeploymentManagement extends PermissionSupport {
*/
@PreAuthorize(HAS_UPDATE_TARGET_AND_READ_DISTRIBUTION_SET)
List<DistributionSetAssignmentResult> assignDistributionSets(
String initiatedBy, @Valid @NotEmpty List<DeploymentRequest> deploymentRequests, String actionMessage);
@Valid @NotEmpty List<DeploymentRequest> deploymentRequests, @Nullable String actionMessage);
/**
* Registers "offline" assignments. "offline" assignment means adding a completed action for a {@link DistributionSet} to a {@link Target}.
@@ -123,9 +105,6 @@ public interface DeploymentManagement extends PermissionSupport {
* @throws MultiAssignmentIsNotEnabledException if the request results in multiple assignments to the same
* target and multi-assignment is disabled
*/
@PreAuthorize(HAS_UPDATE_TARGET_AND_READ_DISTRIBUTION_SET)
List<DistributionSetAssignmentResult> offlineAssignedDistributionSets(String initiatedBy, Collection<Entry<String, Long>> assignments);
@PreAuthorize(HAS_UPDATE_TARGET_AND_READ_DISTRIBUTION_SET)
List<DistributionSetAssignmentResult> offlineAssignedDistributionSets(Collection<Entry<String, Long>> assignments);

View File

@@ -9,8 +9,8 @@
*/
package org.eclipse.hawkbit.repository;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.auth.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation;
import org.springframework.security.access.prepost.PreAuthorize;

View File

@@ -9,13 +9,12 @@
*/
package org.eclipse.hawkbit.repository;
import static org.eclipse.hawkbit.im.authentication.SpringEvalExpressions.HAS_READ_REPOSITORY;
import static org.eclipse.hawkbit.im.authentication.SpringEvalExpressions.HAS_UPDATE_REPOSITORY;
import static org.eclipse.hawkbit.auth.SpringEvalExpressions.HAS_READ_REPOSITORY;
import static org.eclipse.hawkbit.auth.SpringEvalExpressions.HAS_UPDATE_REPOSITORY;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import jakarta.validation.constraints.NotEmpty;
@@ -27,7 +26,7 @@ import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;

View File

@@ -16,7 +16,7 @@ import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.Tag;

View File

@@ -21,8 +21,8 @@ import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.auth.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;

View File

@@ -15,7 +15,7 @@ import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.auth.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;

View File

@@ -19,7 +19,7 @@ import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.auth.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;

View File

@@ -9,7 +9,7 @@
*/
package org.eclipse.hawkbit.repository;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.auth.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.Rollout.RolloutStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroup;

View File

@@ -9,12 +9,10 @@
*/
package org.eclipse.hawkbit.repository;
import java.util.Optional;
import jakarta.validation.constraints.NotNull;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.auth.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;

View File

@@ -9,8 +9,7 @@
*/
package org.eclipse.hawkbit.repository;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.auth.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.Target;

View File

@@ -28,8 +28,8 @@ import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import lombok.experimental.SuperBuilder;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.auth.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.EntityReadOnlyException;

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2025 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.security.crypto.codec.Hex;
import org.springframework.security.crypto.keygen.BytesKeyGenerator;
import org.springframework.security.crypto.keygen.KeyGenerators;
/**
* A singleton bean which holds the {@link SecurityTokenGenerator} and make it
* accessible to beans which are not managed by spring, e.g. JPA entities.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings("java:S6548") // java:S6548 - singleton holder ensures static access to spring resources in some places
public final class SecurityTokenGenerator {
private static final int TOKEN_LENGTH = 16;
private static final BytesKeyGenerator SECURE_RANDOM = KeyGenerators.secureRandom(TOKEN_LENGTH);
/**
* Generates a random secure token of {@link #TOKEN_LENGTH} bytes length as hexadecimal string.
*
* @return a new generated random alphanumeric string.
*/
public static String generateToken() {
return new String(Hex.encode(SECURE_RANDOM.generateKey()));
}
}

View File

@@ -1,49 +0,0 @@
/**
* Copyright (c) 2025 Contributors to the Eclipse Foundation
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
import org.springframework.beans.factory.annotation.Autowired;
/**
* A singleton bean which holds the {@link SecurityTokenGenerator} and make it
* accessible to beans which are not managed by spring, e.g. JPA entities.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings("java:S6548") // java:S6548 - singleton holder ensures static access to spring resources in some places
public final class SecurityTokenGeneratorHolder {
private static final SecurityTokenGeneratorHolder SINGLETON = new SecurityTokenGeneratorHolder();
private SecurityTokenGenerator securityTokenGenerator;
/**
* @return a singleton instance of the security token generator holder.
*/
public static SecurityTokenGeneratorHolder getInstance() {
return SINGLETON;
}
@Autowired // spring setter injection
public void setSecurityTokenGenerator(final SecurityTokenGenerator securityTokenGenerator) {
this.securityTokenGenerator = securityTokenGenerator;
}
/**
* delegates to {@link SecurityTokenGenerator#generateToken()}.
*
* @return the result {@link SecurityTokenGenerator#generateToken()}
*/
public String generateToken() {
return securityTokenGenerator.generateToken();
}
}

View File

@@ -20,8 +20,8 @@ import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.auth.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.NamedEntity;

View File

@@ -20,8 +20,8 @@ import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.auth.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.Type;

View File

@@ -13,14 +13,12 @@ import java.util.function.Consumer;
import jakarta.validation.constraints.NotNull;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.auth.SpringEvalExpressions;
import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.TenantMetaData;
import org.eclipse.hawkbit.repository.model.report.SystemUsageReport;
import org.eclipse.hawkbit.repository.model.report.SystemUsageReportWithTenants;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -30,14 +28,6 @@ import org.springframework.security.access.prepost.PreAuthorize;
*/
public interface SystemManagement {
/**
* Deletes all data related to a given tenant.
*
* @param tenant to delete
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN)
void deleteTenant(@NotNull String tenant);
/**
* @param pageable for paging information
* @return list of all tenant names in the system.
@@ -47,7 +37,7 @@ public interface SystemManagement {
/**
* Runs consumer for each tenant as
* {@link TenantAware#runAsTenant(String, java.util.concurrent.Callable)}
* {@link AccessContext#asSystemAsTenant(String, java.util.concurrent.Callable)}
* silently (i.e. exceptions will be logged but operations will continue for further tenants).
*
* @param consumer to run as tenant
@@ -56,37 +46,15 @@ public interface SystemManagement {
void forEachTenant(Consumer<String> consumer);
/**
* Calculated system usage statistics, both overall for the entire system and per tenant;
*
* @return SystemUsageReport of the current system
* @return {@link TenantMetaData} of {@link AccessContext#tenant()}
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN)
SystemUsageReportWithTenants getSystemUsageStatisticsWithTenants();
/**
* Calculated overall system usage statistics
*
* @return SystemUsageReport of the current system
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN)
SystemUsageReport getSystemUsageStatistics();
/**
* @return {@link TenantMetaData} of {@link TenantAware#getCurrentTenant()}
*/
@PreAuthorize("hasAuthority('" + SpPermission.READ_DISTRIBUTION_SET + "')" + " or "
+ "hasAuthority('READ_" + SpPermission.TARGET + "')" + " or "
+ "hasAuthority('READ_" + SpPermission.TENANT_CONFIGURATION + "')" + " or "
+ SpringEvalExpressions.IS_CONTROLLER)
@PreAuthorize("hasAuthority('" + SpPermission.READ_DISTRIBUTION_SET + "')" + " or " + "hasAuthority('READ_" + SpPermission.TARGET + "')" + " or " + "hasAuthority('READ_" + SpPermission.TENANT_CONFIGURATION + "')" + " or " + SpringEvalExpressions.IS_CONTROLLER)
TenantMetaData getTenantMetadata();
/**
* @return {@link TenantMetaData} of {@link TenantAware#getCurrentTenant()} without details ({@link TenantMetaData#getDefaultDsType()})
* @return {@link TenantMetaData} of {@link AccessContext#tenant()} without details ({@link TenantMetaData#getDefaultDsType()})
*/
@PreAuthorize("hasAuthority('" + SpPermission.READ_DISTRIBUTION_SET + "')" + " or "
+ "hasAuthority('READ_" + SpPermission.TARGET + "')" + " or "
+ "hasAuthority('READ_" + SpPermission.TENANT_CONFIGURATION + "')" + " or "
+ SpringEvalExpressions.IS_CONTROLLER)
@PreAuthorize("hasAuthority('" + SpPermission.READ_DISTRIBUTION_SET + "')" + " or " + "hasAuthority('READ_" + SpPermission.TARGET + "')" + " or " + "hasAuthority('READ_" + SpPermission.TENANT_CONFIGURATION + "')" + " or " + SpringEvalExpressions.IS_CONTROLLER)
TenantMetaData getTenantMetadataWithoutDetails();
/**
@@ -113,4 +81,12 @@ public interface SystemManagement {
*/
@PreAuthorize("hasAuthority('UPDATE_" + SpPermission.TENANT_CONFIGURATION + "')")
TenantMetaData updateTenantMetadata(long defaultDsType);
/**
* Deletes all data related to a given tenant.
*
* @param tenant to delete
*/
@PreAuthorize(SpringEvalExpressions.HAS_AUTH_SYSTEM_ADMIN)
void deleteTenant(@NotNull String tenant);
}

View File

@@ -22,8 +22,8 @@ import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;
import lombok.experimental.SuperBuilder;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.auth.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;

View File

@@ -9,9 +9,9 @@
*/
package org.eclipse.hawkbit.repository;
import static org.eclipse.hawkbit.im.authentication.SpringEvalExpressions.HAS_DELETE_REPOSITORY;
import static org.eclipse.hawkbit.im.authentication.SpringEvalExpressions.HAS_READ_REPOSITORY;
import static org.eclipse.hawkbit.im.authentication.SpringEvalExpressions.HAS_UPDATE_REPOSITORY;
import static org.eclipse.hawkbit.auth.SpringEvalExpressions.HAS_DELETE_REPOSITORY;
import static org.eclipse.hawkbit.auth.SpringEvalExpressions.HAS_READ_REPOSITORY;
import static org.eclipse.hawkbit.auth.SpringEvalExpressions.HAS_UPDATE_REPOSITORY;
import java.util.Collection;
import java.util.List;
@@ -29,8 +29,8 @@ import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.auth.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
@@ -466,7 +466,7 @@ public interface TargetManagement<T extends Target>
: controllerId
: builder.name;
securityToken = ObjectUtils.isEmpty(builder.securityToken)
? SecurityTokenGeneratorHolder.getInstance().generateToken()
? SecurityTokenGenerator.generateToken()
: builder.securityToken;
}
}

View File

@@ -16,7 +16,7 @@ import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.repository.model.NamedEntity;
import org.eclipse.hawkbit.repository.model.Tag;
import org.eclipse.hawkbit.repository.model.TargetTag;

View File

@@ -9,7 +9,7 @@
*/
package org.eclipse.hawkbit.repository;
import static org.eclipse.hawkbit.im.authentication.SpringEvalExpressions.HAS_READ_REPOSITORY;
import static org.eclipse.hawkbit.auth.SpringEvalExpressions.HAS_READ_REPOSITORY;
import java.util.Collection;
import java.util.Collections;
@@ -24,8 +24,8 @@ import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.auth.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.exception.TargetTypeKeyOrNameRequiredException;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.NamedEntity;

View File

@@ -13,8 +13,8 @@ import java.io.Serializable;
import java.util.Map;
import java.util.function.Function;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.auth.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.exception.TenantConfigurationValidatorException;
import org.eclipse.hawkbit.repository.model.PollStatus;
import org.eclipse.hawkbit.repository.model.Target;

View File

@@ -9,8 +9,8 @@
*/
package org.eclipse.hawkbit.repository;
import org.eclipse.hawkbit.im.authentication.SpRole;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.auth.SpRole;
import org.eclipse.hawkbit.auth.SpringEvalExpressions;
import org.eclipse.hawkbit.repository.model.report.TenantUsage;
import org.springframework.security.access.prepost.PreAuthorize;

View File

@@ -9,9 +9,6 @@
*/
package org.eclipse.hawkbit.repository;
import java.util.Arrays;
import java.util.Optional;
/**
* Enumerates the supported update modes. Each mode represents an attribute update strategy.
*

View File

@@ -15,7 +15,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.context.ApplicationEvent;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")

View File

@@ -17,7 +17,6 @@ import lombok.NoArgsConstructor;
import lombok.ToString;
import org.eclipse.hawkbit.repository.event.entity.EntityDeletedEvent;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager.CacheEvictEvent;
/**

View File

@@ -1,42 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.helper;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.springframework.beans.factory.annotation.Autowired;
/**
* A singleton bean which holds {@link SystemSecurityContext} service and makes it accessible to beans which are not
* managed by spring, e.g. JPA entities.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings("java:S6548") // java:S6548 - singleton holder ensures static access to spring resources in some places
public final class SystemSecurityContextHolder {
private static final SystemSecurityContextHolder SINGLETON = new SystemSecurityContextHolder();
@Getter
private SystemSecurityContext systemSecurityContext;
/**
* @return the singleton {@link SystemSecurityContextHolder} instance
*/
public static SystemSecurityContextHolder getInstance() {
return SINGLETON;
}
@Autowired // spring setter injection
public void setSystemSecurityContext(final SystemSecurityContext systemSecurityContext) {
this.systemSecurityContext = systemSecurityContext;
}
}

View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2019 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.helper;
import static org.eclipse.hawkbit.context.AccessContext.asSystem;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.USER_CONFIRMATION_FLOW_ENABLED;
import java.io.Serializable;
import java.util.Objects;
import java.util.function.Function;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.model.PollStatus;
import org.eclipse.hawkbit.repository.model.Target;
/**
* A collection of static helper methods for the tenant configuration
*/
@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
public final class TenantConfigHelper {
private static TenantConfigurationManagement tenantConfigurationManagement;
// method to be initialized by the TenantConfigurationManagement or TenantConfigurationManagement creator
// it will be accessed directly and used so shall be fully initialized instance, i.e. a bean in order to onore things
// like @PreAuthorize, @Transactional etc.
public static void setTenantConfigurationManagement(final TenantConfigurationManagement tenantConfigurationManagement) {
TenantConfigHelper.tenantConfigurationManagement = tenantConfigurationManagement;
}
public static TenantConfigurationManagement getTenantConfigurationManagement() {
return Objects.requireNonNull(tenantConfigurationManagement, "TenantConfigurationManagement has not been initialized");
}
public static <T extends Serializable> T getAsSystem(final String key, final Class<T> valueType) {
return asSystem(() -> getTenantConfigurationManagement().getConfigurationValue(key, valueType).getValue());
}
public static boolean isMultiAssignmentsEnabled() {
return getAsSystem(MULTI_ASSIGNMENTS_ENABLED, Boolean.class);
}
public static boolean isUserConfirmationFlowEnabled() {
return getAsSystem(USER_CONFIRMATION_FLOW_ENABLED, Boolean.class);
}
public static Function<Target, PollStatus> pollStatusResolver() {
return getTenantConfigurationManagement().pollStatusResolver();
}
}

View File

@@ -1,42 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.helper;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.springframework.beans.factory.annotation.Autowired;
/**
* A singleton bean which holds {@link TenantConfigurationManagement} service and makes it accessible to beans which are
* not managed by spring, e.g. JPA entities.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings("java:S6548") // java:S6548 - singleton holder ensures static access to spring resources in some places
public final class TenantConfigurationManagementHolder {
private static final TenantConfigurationManagementHolder SINGLETON = new TenantConfigurationManagementHolder();
@Getter
private TenantConfigurationManagement tenantConfigurationManagement;
/**
* @return the singleton {@link TenantConfigurationManagementHolder} instance
*/
public static TenantConfigurationManagementHolder getInstance() {
return SINGLETON;
}
@Autowired // spring setter injection
public void setTenantConfigurationManagement(final TenantConfigurationManagement tenantConfigurationManagement) {
this.tenantConfigurationManagement = tenantConfigurationManagement;
}
}

View File

@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.model;
import java.util.concurrent.TimeUnit;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.repository.Identifiable;
/**
@@ -61,7 +62,7 @@ public interface Target extends NamedEntity, Identifiable<Long> {
/**
* @return the securityToken if the current security context contains the necessary permission
* {@link org.eclipse.hawkbit.im.authentication.SpPermission#READ_TARGET_SECURITY_TOKEN}
* {@link SpPermission#READ_TARGET_SECURITY_TOKEN}
* or the current context is executed as system code, otherwise {@code null}.
*/
String getSecurityToken();

View File

@@ -14,8 +14,7 @@ import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationPrope
import java.time.Duration;
import org.eclipse.hawkbit.repository.helper.SystemSecurityContextHolder;
import org.eclipse.hawkbit.repository.helper.TenantConfigurationManagementHolder;
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
import org.eclipse.hawkbit.tenancy.configuration.DurationHelper;
import org.eclipse.hawkbit.tenancy.configuration.PollingTime;
import org.springframework.util.PropertyPlaceholderHelper;
@@ -81,8 +80,6 @@ public class VirtualPropertyResolver {
}
private static String getRawStringForKey(final String key) {
return SystemSecurityContextHolder.getInstance().getSystemSecurityContext().runAsSystem(
() -> TenantConfigurationManagementHolder.getInstance().getTenantConfigurationManagement()
.getConfigurationValue(key, String.class).getValue());
return TenantConfigHelper.getAsSystem(key, String.class);
}
}

View File

@@ -58,7 +58,7 @@ public class TenantConfigurationProperties {
}
/**
* Tenant specific configurations which can be configured for each tenant separately by means of override of the system defaults.
* AccessContext specific configurations which can be configured for each tenant separately by means of override of the system defaults.
*/
@Data
@ToString
@@ -127,7 +127,7 @@ public class TenantConfigurationProperties {
/**
* Switch to enable/disable the user-confirmation flow
*/
public static final String USER_CONFIRMATION_ENABLED = "user.confirmation.flow.enabled";
public static final String USER_CONFIRMATION_FLOW_ENABLED = "user.confirmation.flow.enabled";
/**
* Switch to enable/disable the implicit locking
*/

View File

@@ -1,76 +0,0 @@
/**
* Copyright (c) 2019 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.utils;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.USER_CONFIRMATION_ENABLED;
import java.io.Serializable;
import java.util.function.Function;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.model.PollStatus;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.security.SystemSecurityContext;
/**
* A collection of static helper methods for the tenant configuration
*/
public final class TenantConfigHelper {
private final TenantConfigurationManagement tenantConfigurationManagement;
private final SystemSecurityContext systemSecurityContext;
private TenantConfigHelper(
final SystemSecurityContext systemSecurityContext,
final TenantConfigurationManagement tenantConfigurationManagement) {
this.systemSecurityContext = systemSecurityContext;
this.tenantConfigurationManagement = tenantConfigurationManagement;
}
/**
* Setting the context of the tenant.
*
* @param systemSecurityContext Security context used to get the tenant and for execution
* @param tenantConfigurationManagement to get the value from
* @return is active
*/
public static TenantConfigHelper usingContext(
final SystemSecurityContext systemSecurityContext,
final TenantConfigurationManagement tenantConfigurationManagement) {
return new TenantConfigHelper(systemSecurityContext, tenantConfigurationManagement);
}
public <T extends Serializable> T getConfigValue(final String key, final Class<T> valueType) {
return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement.getConfigurationValue(key, valueType).getValue());
}
/**
* Is multi-assignments enabled for the current tenant
*
* @return is active
*/
public boolean isMultiAssignmentsEnabled() {
return getConfigValue(MULTI_ASSIGNMENTS_ENABLED, Boolean.class);
}
/**
* Is confirmation flow enabled for the current tenant
*
* @return is enabled
*/
public boolean isConfirmationFlowEnabled() {
return getConfigValue(USER_CONFIRMATION_ENABLED, Boolean.class);
}
public Function<Target, PollStatus> pollStatusResolver() {
return tenantConfigurationManagement.pollStatusResolver();
}
}

View File

@@ -9,8 +9,11 @@
*/
package org.eclipse.hawkbit.event;
import static org.eclipse.hawkbit.context.AccessContext.asSystemAsTenant;
import java.util.concurrent.Executor;
import java.util.function.Consumer;
import io.protostuff.ProtostuffIOUtil;
import io.protostuff.Schema;
import lombok.extern.slf4j.Slf4j;
@@ -19,7 +22,6 @@ import org.eclipse.hawkbit.repository.event.EventPublisherHolder;
import org.eclipse.hawkbit.repository.event.remote.AbstractRemoteEvent;
import org.eclipse.hawkbit.repository.event.remote.RemoteTenantAwareEvent;
import org.eclipse.hawkbit.repository.event.remote.service.AbstractServiceRemoteEvent;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -47,10 +49,9 @@ public class EventPublisherConfiguration {
*/
@Bean(name = AbstractApplicationContext.APPLICATION_EVENT_MULTICASTER_BEAN_NAME)
ApplicationEventMulticaster applicationEventMulticaster(
@Qualifier("asyncExecutor") final Executor executor,
final SystemSecurityContext systemSecurityContext, final ApplicationEventFilter applicationEventFilter) {
@Qualifier("asyncExecutor") final Executor executor, final ApplicationEventFilter applicationEventFilter) {
final SimpleApplicationEventMulticaster simpleApplicationEventMulticaster =
new TenantAwareApplicationEventPublisher(systemSecurityContext, applicationEventFilter);
new TenantAwareApplicationEventPublisher(applicationEventFilter);
simpleApplicationEventMulticaster.setTaskExecutor(executor);
return simpleApplicationEventMulticaster;
}
@@ -76,17 +77,14 @@ public class EventPublisherConfiguration {
private static class TenantAwareApplicationEventPublisher extends SimpleApplicationEventMulticaster {
private final SystemSecurityContext systemSecurityContext;
private final ApplicationEventFilter applicationEventFilter;
protected TenantAwareApplicationEventPublisher(
final SystemSecurityContext systemSecurityContext, final ApplicationEventFilter applicationEventFilter) {
this.systemSecurityContext = systemSecurityContext;
protected TenantAwareApplicationEventPublisher(final ApplicationEventFilter applicationEventFilter) {
this.applicationEventFilter = applicationEventFilter;
}
/**
* Was overridden that not every event has to run within an own tenantAware.
* Was overridden that not every event has to run within an own tenant.
*/
@Override
public void multicastEvent(final ApplicationEvent event, final ResolvableType eventType) {
@@ -95,19 +93,13 @@ public class EventPublisherConfiguration {
}
if (event instanceof final RemoteTenantAwareEvent remoteEvent) {
systemSecurityContext.runAsSystemAsTenant(() -> {
super.multicastEvent(event, eventType);
return null;
}, remoteEvent.getTenant());
asSystemAsTenant(remoteEvent.getTenant(), () -> super.multicastEvent(event, eventType));
return;
}
if (event instanceof final AbstractServiceRemoteEvent<?> serviceRemoteEvent
&& serviceRemoteEvent.getRemoteEvent() instanceof RemoteTenantAwareEvent tenantAwareEvent) {
systemSecurityContext.runAsSystemAsTenant(() -> {
super.multicastEvent(event, eventType);
return null;
}, tenantAwareEvent.getTenant());
asSystemAsTenant(tenantAwareEvent.getTenant(), () -> super.multicastEvent(event, eventType));
return;
}

View File

@@ -26,15 +26,6 @@ import org.eclipse.hawkbit.repository.event.remote.DistributionSetTypeDeletedEve
import org.eclipse.hawkbit.repository.event.remote.DownloadProgressEvent;
import org.eclipse.hawkbit.repository.event.remote.MultiActionAssignEvent;
import org.eclipse.hawkbit.repository.event.remote.MultiActionCancelEvent;
import org.eclipse.hawkbit.repository.event.remote.service.ActionCreatedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.ActionUpdatedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.CancelTargetAssignmentServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.MultiActionAssignServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.MultiActionCancelServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetAssignDistributionSetServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetAttributesRequestedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetCreatedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetDeletedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.RolloutDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.RolloutGroupDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.RolloutStoppedEvent;
@@ -74,6 +65,15 @@ import org.eclipse.hawkbit.repository.event.remote.entity.TargetTypeUpdatedEvent
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TenantConfigurationCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TenantConfigurationUpdatedEvent;
import org.eclipse.hawkbit.repository.event.remote.service.ActionCreatedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.ActionUpdatedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.CancelTargetAssignmentServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.MultiActionAssignServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.MultiActionCancelServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetAssignDistributionSetServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetAttributesRequestedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetCreatedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetDeletedServiceEvent;
import org.eclipse.hawkbit.repository.event.remote.service.TargetUpdatedServiceEvent;
/**

View File

@@ -12,8 +12,8 @@ package org.eclipse.hawkbit.repository;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.im.authentication.Hierarchy;
import org.eclipse.hawkbit.im.authentication.SpringEvalExpressions;
import org.eclipse.hawkbit.auth.Hierarchy;
import org.eclipse.hawkbit.auth.SpringEvalExpressions;
import org.eclipse.hawkbit.tenancy.configuration.ControllerPollProperties;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
import org.springframework.beans.factory.annotation.Value;
@@ -47,8 +47,7 @@ public class RepositoryConfiguration {
@Bean
@ConditionalOnMissingBean
@SuppressWarnings("java:S3358")
// java:S3358 better readable this way
@SuppressWarnings("java:S3358") // java:S3358 better readable this way
RoleHierarchy roleHierarchy(
// if configured replaces the hierarchy completely
@Value("${hawkbit.hierarchy:}") final String hierarchy,
@@ -62,6 +61,9 @@ public class RepositoryConfiguration {
@Bean
PermissionEvaluator permissionEvaluator(final RoleHierarchy roleHierarchy) {
// sets up global access to the role hierarchy
Hierarchy.setRoleHierarchy(roleHierarchy);
// and returns a custom permission evaluator
return new DenyAllPermissionEvaluator() {
@Override
@@ -72,7 +74,8 @@ public class RepositoryConfiguration {
.replace(SpringEvalExpressions.PERMISSION_GROUP_PLACEHOLDER, permissionSupport.permissionGroup());
// do permissions check
final boolean hasPermission = roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities()).stream()
final boolean hasPermission = Hierarchy.getRoleHierarchy()
.getReachableGrantedAuthorities(authentication.getAuthorities()).stream()
.map(GrantedAuthority::getAuthority)
.anyMatch(authority -> authority.equals(neededPermission));
if (!hasPermission) {

View File

@@ -9,6 +9,8 @@
*/
package org.eclipse.hawkbit.repository;
import static org.eclipse.hawkbit.context.AccessContext.asSystemAsTenant;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -25,7 +27,6 @@ import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.cache.Cache;
import org.springframework.context.event.EventListener;
@@ -39,19 +40,13 @@ public class RolloutStatusCache {
private static final TenantAwareCacheManager CACHE_MANAGER = TenantAwareCacheManager.getInstance();
private final TenantAware tenantAware;
public RolloutStatusCache(final TenantAware tenantAware) {
this.tenantAware = tenantAware;
}
/**
* Retrieves cached list of {@link TotalTargetCountActionStatus} of {@link Rollout}s.
*
* @param rollouts rolloutIds to retrieve cache entries for
* @return map of cached entries
*/
public Map<Long, List<TotalTargetCountActionStatus>> getRolloutStatus(final List<Long> rollouts) {
public static Map<Long, List<TotalTargetCountActionStatus>> getRolloutStatus(final List<Long> rollouts) {
return retrieveFromCache(rollouts, getRolloutStatusCache());
}
@@ -61,7 +56,7 @@ public class RolloutStatusCache {
* @param rolloutId to retrieve cache entries for
* @return map of cached entries
*/
public List<TotalTargetCountActionStatus> getRolloutStatus(final Long rolloutId) {
public static List<TotalTargetCountActionStatus> getRolloutStatus(final Long rolloutId) {
return retrieveFromCache(rolloutId, getRolloutStatusCache());
}
@@ -71,7 +66,7 @@ public class RolloutStatusCache {
* @param rolloutGroups rolloutGroupsIds to retrieve cache entries for
* @return map of cached entries
*/
public Map<Long, List<TotalTargetCountActionStatus>> getRolloutGroupStatus(final List<Long> rolloutGroups) {
public static Map<Long, List<TotalTargetCountActionStatus>> getRolloutGroupStatus(final List<Long> rolloutGroups) {
return retrieveFromCache(rolloutGroups, getGroupStatusCache());
}
@@ -81,7 +76,7 @@ public class RolloutStatusCache {
* @param groupId to retrieve cache entries for
* @return map of cached entries
*/
public List<TotalTargetCountActionStatus> getRolloutGroupStatus(final Long groupId) {
public static List<TotalTargetCountActionStatus> getRolloutGroupStatus(final Long groupId) {
return retrieveFromCache(groupId, getGroupStatusCache());
}
@@ -91,7 +86,7 @@ public class RolloutStatusCache {
*
* @param put map of cached entries
*/
public void putRolloutStatus(final Map<Long, List<TotalTargetCountActionStatus>> put) {
public static void putRolloutStatus(final Map<Long, List<TotalTargetCountActionStatus>> put) {
putIntoCache(put, getRolloutStatusCache());
}
@@ -101,7 +96,7 @@ public class RolloutStatusCache {
* @param rolloutId the cache entries belong to
* @param status list to cache
*/
public void putRolloutStatus(final Long rolloutId, final List<TotalTargetCountActionStatus> status) {
public static void putRolloutStatus(final Long rolloutId, final List<TotalTargetCountActionStatus> status) {
putIntoCache(rolloutId, status, getRolloutStatusCache());
}
@@ -110,7 +105,7 @@ public class RolloutStatusCache {
*
* @param put map of cached entries
*/
public void putRolloutGroupStatus(final Map<Long, List<TotalTargetCountActionStatus>> put) {
public static void putRolloutGroupStatus(final Map<Long, List<TotalTargetCountActionStatus>> put) {
putIntoCache(put, getGroupStatusCache());
}
@@ -120,51 +115,51 @@ public class RolloutStatusCache {
* @param groupId the cache entries belong to
* @param status list to cache
*/
public void putRolloutGroupStatus(final Long groupId, final List<TotalTargetCountActionStatus> status) {
public static void putRolloutGroupStatus(final Long groupId, final List<TotalTargetCountActionStatus> status) {
putIntoCache(groupId, status, getGroupStatusCache());
}
@EventListener(classes = AbstractActionEvent.class)
public void invalidateCachedTotalTargetCountActionStatus(final AbstractActionEvent event) {
if (event.getRolloutId() != null) {
final Cache cache = tenantAware.runAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_RO_NAME));
final Cache cache = asSystemAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_RO_NAME));
cache.evict(event.getRolloutId());
}
if (event.getRolloutGroupId() != null) {
final Cache cache = tenantAware.runAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_GR_NAME));
final Cache cache = asSystemAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_GR_NAME));
cache.evict(event.getRolloutGroupId());
}
}
@EventListener(classes = RolloutDeletedEvent.class)
public void invalidateCachedTotalTargetCountOnRolloutDelete(final RolloutDeletedEvent event) {
final Cache cache = tenantAware.runAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_RO_NAME));
final Cache cache = asSystemAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_RO_NAME));
cache.evict(event.getEntityId());
}
@EventListener(classes = RolloutGroupDeletedEvent.class)
public void invalidateCachedTotalTargetCountOnRolloutGroupDelete(final RolloutGroupDeletedEvent event) {
final Cache cache = tenantAware.runAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_GR_NAME));
final Cache cache = asSystemAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_GR_NAME));
cache.evict(event.getEntityId());
}
@EventListener(classes = RolloutStoppedEvent.class)
public void invalidateCachedTotalTargetCountOnRolloutStopped(final RolloutStoppedEvent event) {
final Cache cache = tenantAware.runAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_RO_NAME));
final Cache cache = asSystemAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_RO_NAME));
cache.evict(event.getRolloutId());
event.getRolloutGroupIds().forEach(
groupId -> tenantAware.runAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_GR_NAME)).evict(groupId));
groupId -> asSystemAsTenant(event.getTenant(), () -> CACHE_MANAGER.getCache(CACHE_GR_NAME)).evict(groupId));
}
private @NotNull Map<Long, List<TotalTargetCountActionStatus>> retrieveFromCache(final List<Long> ids, @NotNull final Cache cache) {
private static @NotNull Map<Long, List<TotalTargetCountActionStatus>> retrieveFromCache(final List<Long> ids, @NotNull final Cache cache) {
return ids.stream()
.map(id -> cache.get(id, CachedTotalTargetCountActionStatus.class))
.filter(Objects::nonNull)
.collect(Collectors.toMap(CachedTotalTargetCountActionStatus::id, CachedTotalTargetCountActionStatus::status));
}
private List<TotalTargetCountActionStatus> retrieveFromCache(final Long id, @NotNull final Cache cache) {
private static List<TotalTargetCountActionStatus> retrieveFromCache(final Long id, @NotNull final Cache cache) {
final CachedTotalTargetCountActionStatus cacheItem = cache.get(id, CachedTotalTargetCountActionStatus.class);
if (cacheItem == null) {
return Collections.emptyList();
@@ -172,20 +167,19 @@ public class RolloutStatusCache {
return cacheItem.status();
}
private void putIntoCache(final Long id, final List<TotalTargetCountActionStatus> status,
@NotNull final Cache cache) {
private static void putIntoCache(final Long id, final List<TotalTargetCountActionStatus> status, @NotNull final Cache cache) {
cache.put(id, new CachedTotalTargetCountActionStatus(id, status));
}
private void putIntoCache(final Map<Long, List<TotalTargetCountActionStatus>> put, @NotNull final Cache cache) {
private static void putIntoCache(final Map<Long, List<TotalTargetCountActionStatus>> put, @NotNull final Cache cache) {
put.forEach((k, v) -> cache.put(k, new CachedTotalTargetCountActionStatus(k, v)));
}
private @NotNull Cache getRolloutStatusCache() {
private static @NotNull Cache getRolloutStatusCache() {
return Objects.requireNonNull(CACHE_MANAGER.getCache(CACHE_RO_NAME), "Cache '" + CACHE_RO_NAME + "' is null!");
}
private @NotNull Cache getGroupStatusCache() {
private static @NotNull Cache getGroupStatusCache() {
return Objects.requireNonNull(CACHE_MANAGER.getCache(CACHE_GR_NAME), "Cache '" + CACHE_RO_NAME + "' is null!");
}

View File

@@ -9,6 +9,11 @@
*/
package org.eclipse.hawkbit.event;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import java.util.HashMap;
import org.eclipse.hawkbit.repository.event.remote.AbstractRemoteEvent;
import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.ActionCreatedEvent;
@@ -22,18 +27,12 @@ import org.eclipse.hawkbit.repository.event.remote.service.TargetDeletedServiceE
import org.eclipse.hawkbit.repository.event.remote.service.TargetUpdatedServiceEvent;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Target;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.MessageConverter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import java.util.HashMap;
abstract class AbstractEventMessageConverterTest {
protected final MessageConverter messageConverter;

View File

@@ -1,47 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.jpa.model.helper;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.beans.factory.annotation.Autowired;
/**
* A singleton bean which holds {@link TenantAware} service and makes it
* accessible to beans which are not managed by spring, e.g. JPA entities.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@SuppressWarnings("java:S6548") // java:S6548 - singleton holder ensures static access to spring resources in some places
public final class TenantAwareHolder {
private static final TenantAwareHolder SINGLETON = new TenantAwareHolder();
private TenantAware tenantAware;
/**
* @return the singleton {@link TenantAwareHolder} instance
*/
public static TenantAwareHolder getInstance() {
return SINGLETON;
}
@Autowired // spring setter injection
public void setTenantAware(final TenantAware tenantAware) {
this.tenantAware = tenantAware;
}
/**
* @return the {@link TenantAware} service
*/
public TenantAware getTenantAware() {
return tenantAware;
}
}

View File

@@ -11,7 +11,6 @@ package org.eclipse.hawkbit.repository.jpa;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.stream.IntStream;
import jakarta.persistence.Query;

View File

@@ -15,7 +15,6 @@ import java.util.Map;
import javax.sql.DataSource;
import lombok.Data;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.orm.jpa.JpaBaseConfiguration;
@@ -45,7 +44,6 @@ public class JpaConfiguration extends JpaBaseConfiguration {
private final Map<String, String> eclipselink = new HashMap<>();
}
private final TenantAware.TenantResolver tenantResolver;
// only for testing purposes ddl generation may be enabled
private final Map<String, String> eclipselinkProperties;
@@ -53,10 +51,8 @@ public class JpaConfiguration extends JpaBaseConfiguration {
protected JpaConfiguration(
final DataSource dataSource, final JpaProperties properties,
final ObjectProvider<JtaTransactionManager> jtaTransactionManagerProvider,
final TenantAware.TenantResolver tenantResolver,
final Properties eclipselinkProperties) {
super(dataSource, properties, jtaTransactionManagerProvider);
this.tenantResolver = tenantResolver;
this.eclipselinkProperties = eclipselinkProperties.getEclipselink();
}
@@ -67,7 +63,7 @@ public class JpaConfiguration extends JpaBaseConfiguration {
@Override
@Bean
public PlatformTransactionManager transactionManager(final ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
return new TransactionManager(tenantResolver);
return new TransactionManager();
}
@Override

View File

@@ -15,9 +15,9 @@ import java.util.Objects;
import jakarta.persistence.EntityManager;
import jakarta.transaction.Transaction;
import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.repository.jpa.model.EntityPropertyChangeListener;
import org.eclipse.hawkbit.repository.jpa.utils.ExceptionMapper;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.persistence.config.PersistenceUnitProperties;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.DescriptorEventManager;
@@ -29,7 +29,7 @@ import org.springframework.transaction.support.DefaultTransactionStatus;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/**
* {@link org.springframework.orm.jpa.JpaTransactionManager} that sets the {@link TenantAware#getCurrentTenant()} in the eclipselink session. This has
* {@link org.springframework.orm.jpa.JpaTransactionManager} that sets the {@link AccessContext#tenant()} in the eclipselink session. This has
* to be done in eclipselink after a {@link Transaction} has been started.
* <p/>
* The class also handles setting:
@@ -45,8 +45,6 @@ class TransactionManager extends JpaTransactionManager {
private static final Class<?> JPA_TARGET;
private transient TenantAware.TenantResolver tenantResolver;
static {
try {
JPA_TARGET = Class.forName("org.eclipse.hawkbit.repository.jpa.model.JpaTarget");
@@ -57,10 +55,6 @@ class TransactionManager extends JpaTransactionManager {
}
}
TransactionManager(final TenantAware.TenantResolver tenantResolver) {
this.tenantResolver = tenantResolver;
}
private static final EntityPropertyChangeListener ENTITY_PROPERTY_CHANGE_LISTENER = new EntityPropertyChangeListener();
@Override
@@ -83,7 +77,7 @@ class TransactionManager extends JpaTransactionManager {
}
}
final String currentTenant = tenantResolver.resolveTenant();
final String currentTenant = AccessContext.tenant();
if (currentTenant == null) {
cleanupTenant(em);
} else {

View File

@@ -22,8 +22,8 @@ import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.repository.exception.TenantNotExistException;
import org.eclipse.hawkbit.repository.jpa.model.helper.TenantAwareHolder;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.eclipse.persistence.annotations.Multitenant;
import org.eclipse.persistence.annotations.MultitenantType;
@@ -50,7 +50,7 @@ public abstract class AbstractJpaTenantAwareBaseEntity extends AbstractJpaBaseEn
private String tenant;
/**
* Tenant aware entities extend the equals/hashcode strategy with the tenant name. That would allow for instance in a
* AccessContext aware entities extend the equals/hashcode strategy with the tenant name. That would allow for instance in a
* multi-schema based data separation setup to have the same primary key for different entities of different tenants.
*/
@Override
@@ -59,7 +59,7 @@ public abstract class AbstractJpaTenantAwareBaseEntity extends AbstractJpaBaseEn
}
/**
* Tenant aware entities extend the equals/hashcode strategy with the tenant name. That would allow for instance in a
* AccessContext aware entities extend the equals/hashcode strategy with the tenant name. That would allow for instance in a
* multi-schema based data separation setup to have the same primary key for different entities of
* different tenants.
*/
@@ -86,13 +86,13 @@ public abstract class AbstractJpaTenantAwareBaseEntity extends AbstractJpaBaseEn
*/
@PrePersist
void prePersist() {
// before persisting the entity check the current ID of the tenant by using the TenantAware service
final String currentTenant = TenantAwareHolder.getInstance().getTenantAware().getCurrentTenant();
// before persisting the entity check the current ID of the tenant by using the AccessContext
final String currentTenant = AccessContext.tenant();
if (currentTenant == null) {
throw new TenantNotExistException(
String.format(
"Tenant %s does not exists, cannot create entity %s with id %d",
TenantAwareHolder.getInstance().getTenantAware().getCurrentTenant(), getClass(), getId()));
"AccessContext %s does not exists, cannot create entity %s with id %d",
AccessContext.tenant(), getClass(), getId()));
}
setTenant(currentTenant.toUpperCase());
}

View File

@@ -10,7 +10,6 @@
package org.eclipse.hawkbit.repository.jpa;
import java.util.Collection;
import java.util.List;
import jakarta.persistence.Query;

View File

@@ -13,12 +13,12 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import lombok.Data;
import org.eclipse.hawkbit.repository.jpa.model.EntityPropertyChangeListener;
import org.eclipse.hawkbit.repository.jpa.utils.ExceptionMapper;
import org.eclipse.hawkbit.repository.jpa.utils.JpaExceptionTranslator;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.hibernate.boot.Metadata;
import org.hibernate.boot.spi.BootstrapContext;
import org.hibernate.cfg.MultiTenancySettings;
@@ -41,13 +41,10 @@ import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.vendor.AbstractJpaVendorAdapter;
import org.springframework.orm.jpa.vendor.HibernateJpaDialect;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.security.core.parameters.P;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.jta.JtaTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;
import javax.sql.DataSource;
/**
* General Hibernate configuration for hawkBit's Repository.
*/
@@ -68,10 +65,9 @@ public class JpaConfiguration extends JpaBaseConfiguration {
protected JpaConfiguration(
final DataSource dataSource, final JpaProperties properties,
final ObjectProvider<JtaTransactionManager> jtaTransactionManagerProvider,
final TenantAware.TenantResolver tenantResolver,
final Properties hibernateProperties) {
super(dataSource, properties, jtaTransactionManagerProvider);
tenantIdentifier = new TenantIdentifier(tenantResolver);
tenantIdentifier = new TenantIdentifier();
this.hibernateProperties = hibernateProperties.getHibernate();
}

View File

@@ -11,26 +11,20 @@ package org.eclipse.hawkbit.repository.jpa;
import java.util.Optional;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.context.AccessContext;
import org.hibernate.context.spi.CurrentTenantIdentifierResolver;
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
/**
* {@link CurrentTenantIdentifierResolver} and {@link HibernatePropertiesCustomizer} that resolves the
* {@link TenantAware#getCurrentTenant()} for hibernate.
* {@link AccessContext#tenant()} for hibernate.
*/
class TenantIdentifier implements CurrentTenantIdentifierResolver<String> {
private final TenantAware.TenantResolver tenantResolver;
TenantIdentifier(final TenantAware.TenantResolver tenantResolver) {
this.tenantResolver = tenantResolver;
}
@Override
public String resolveCurrentTenantIdentifier() {
// on bootstrapping hibernate requests tenant and want to be non-null
return Optional.ofNullable(tenantResolver.resolveTenant()).map(String::toUpperCase).orElse("");
return Optional.ofNullable(AccessContext.tenant()).map(String::toUpperCase).orElse("");
}
@Override

View File

@@ -22,8 +22,8 @@ import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.repository.exception.TenantNotExistException;
import org.eclipse.hawkbit.repository.jpa.model.helper.TenantAwareHolder;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.hibernate.annotations.TenantId;
@@ -46,7 +46,7 @@ public abstract class AbstractJpaTenantAwareBaseEntity extends AbstractJpaBaseEn
private String tenant;
/**
* Tenant aware entities extend the equals/hashcode strategy with the tenant name. That would allow for instance in a
* AccessContext aware entities extend the equals/hashcode strategy with the tenant name. That would allow for instance in a
* multi-schema based data separation setup to have the same primary key for different entities of different tenants.
*/
@Override
@@ -55,7 +55,7 @@ public abstract class AbstractJpaTenantAwareBaseEntity extends AbstractJpaBaseEn
}
/**
* Tenant aware entities extend the equals/hashcode strategy with the tenant name. That would allow for instance in a
* AccessContext aware entities extend the equals/hashcode strategy with the tenant name. That would allow for instance in a
* multi-schema based data separation setup to have the same primary key for different entities of
* different tenants.
*/
@@ -82,13 +82,13 @@ public abstract class AbstractJpaTenantAwareBaseEntity extends AbstractJpaBaseEn
*/
@PrePersist
void prePersist() {
// before persisting the entity check the current ID of the tenant by using the TenantAware service
final String currentTenant = TenantAwareHolder.getInstance().getTenantAware().getCurrentTenant();
// before persisting the entity check the current ID of the tenant by using the AccessContext
final String currentTenant = AccessContext.tenant();
if (currentTenant == null) {
throw new TenantNotExistException(
String.format(
"Tenant %s does not exists, cannot create entity %s with id %d",
TenantAwareHolder.getInstance().getTenantAware().getCurrentTenant(), getClass(), getId()));
"AccessContext %s does not exists, cannot create entity %s with id %d",
AccessContext.tenant(), getClass(), getId()));
}
setTenant(currentTenant.toUpperCase());
}

View File

@@ -11,11 +11,12 @@ package org.eclipse.hawkbit.repository.jpa;
import java.util.Collection;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.repository.RolloutApprovalStrategy;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
@@ -26,34 +27,19 @@ import org.springframework.security.core.context.SecurityContextHolder;
* as the roles of the user who created the Rollout. Provides a no-operation implementation of
* {@link RolloutApprovalStrategy#onApprovalRequired(Rollout)}.
*/
@NoArgsConstructor(access = AccessLevel.PACKAGE)
public class DefaultRolloutApprovalStrategy implements RolloutApprovalStrategy {
private final TenantConfigurationManagement tenantConfigurationManagement;
private final SystemSecurityContext systemSecurityContext;
DefaultRolloutApprovalStrategy(
final TenantConfigurationManagement tenantConfigurationManagement,
final SystemSecurityContext systemSecurityContext) {
this.tenantConfigurationManagement = tenantConfigurationManagement;
this.systemSecurityContext = systemSecurityContext;
}
/**
* Returns true, if rollout approval is enabled and rollout creator doesn't have approval role. It have to be called in the user context
* Returns true, if rollout approval is enabled and rollout creator doesn't have approval role. It has to be called in the user context
*/
@Override
public boolean isApprovalNeeded(final Rollout rollout) {
return isApprovalEnabled() && hasNoApproveRolloutPermission(
getCurrentAuthentication().getAuthorities().stream().map(GrantedAuthority::getAuthority).toList());
return TenantConfigHelper.getAsSystem(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, Boolean.class) &&
hasNoApproveRolloutPermission(
getCurrentAuthentication().getAuthorities().stream().map(GrantedAuthority::getAuthority).toList());
}
/***
* Per default do nothing.
*
* @param rollout
* rollout to create approval task for.
*/
@Override
public void onApprovalRequired(final Rollout rollout) {
// do nothing per default, can be extended by further implementations.
@@ -71,9 +57,4 @@ public class DefaultRolloutApprovalStrategy implements RolloutApprovalStrategy {
private static boolean hasNoApproveRolloutPermission(final Collection<String> authorities) {
return authorities.stream().noneMatch(SpPermission.APPROVE_ROLLOUT::equals);
}
private boolean isApprovalEnabled() {
return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement
.getConfigurationValue(TenantConfigurationKey.ROLLOUT_APPROVAL_ENABLED, Boolean.class).getValue());
}
}

View File

@@ -39,15 +39,12 @@ import org.eclipse.hawkbit.repository.RolloutExecutor;
import org.eclipse.hawkbit.repository.RolloutHandler;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.RolloutStatusCache;
import org.eclipse.hawkbit.repository.SecurityTokenGeneratorHolder;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.event.ApplicationEventFilter;
import org.eclipse.hawkbit.repository.event.remote.EventEntityManager;
import org.eclipse.hawkbit.repository.event.remote.EventEntityManagerHolder;
import org.eclipse.hawkbit.repository.event.remote.TargetPollEvent;
import org.eclipse.hawkbit.repository.helper.SystemSecurityContextHolder;
import org.eclipse.hawkbit.repository.helper.TenantConfigurationManagementHolder;
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.aspects.ExceptionMappingAspectHandler;
import org.eclipse.hawkbit.repository.jpa.autocleanup.AutoActionCleanup;
@@ -64,7 +61,6 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType;
import org.eclipse.hawkbit.repository.jpa.model.helper.EntityInterceptorHolder;
import org.eclipse.hawkbit.repository.jpa.model.helper.TenantAwareHolder;
import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository;
import org.eclipse.hawkbit.repository.jpa.repository.ArtifactRepository;
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetRepository;
@@ -82,7 +78,6 @@ import org.eclipse.hawkbit.repository.jpa.rollout.condition.StartNextGroupRollou
import org.eclipse.hawkbit.repository.jpa.rollout.condition.ThresholdRolloutGroupErrorCondition;
import org.eclipse.hawkbit.repository.jpa.rollout.condition.ThresholdRolloutGroupSuccessCondition;
import org.eclipse.hawkbit.repository.jpa.scheduler.AutoAssignScheduler;
import org.eclipse.hawkbit.repository.jpa.scheduler.JpaAutoAssignExecutor;
import org.eclipse.hawkbit.repository.jpa.scheduler.JpaRolloutHandler;
import org.eclipse.hawkbit.repository.jpa.scheduler.RolloutScheduler;
import org.eclipse.hawkbit.repository.jpa.utils.ExceptionMapper;
@@ -90,9 +85,6 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyResolver;
import org.eclipse.hawkbit.security.HawkbitSecurityProperties;
import org.eclipse.hawkbit.security.SecurityTokenGenerator;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
@@ -222,17 +214,15 @@ public class JpaRepositoryConfiguration {
@Bean
@ConditionalOnMissingBean
PauseRolloutGroupAction pauseRolloutGroupAction(
final RolloutManagement rolloutManagement, final RolloutGroupRepository rolloutGroupRepository,
final SystemSecurityContext systemSecurityContext) {
return new PauseRolloutGroupAction(rolloutManagement, rolloutGroupRepository, systemSecurityContext);
final RolloutManagement rolloutManagement, final RolloutGroupRepository rolloutGroupRepository) {
return new PauseRolloutGroupAction(rolloutManagement, rolloutGroupRepository);
}
@Bean
@ConditionalOnMissingBean
StartNextGroupRolloutGroupSuccessAction startNextRolloutGroupAction(
final RolloutGroupRepository rolloutGroupRepository, final DeploymentManagement deploymentManagement,
final SystemSecurityContext systemSecurityContext) {
return new StartNextGroupRolloutGroupSuccessAction(rolloutGroupRepository, deploymentManagement, systemSecurityContext);
final RolloutGroupRepository rolloutGroupRepository, final DeploymentManagement deploymentManagement) {
return new StartNextGroupRolloutGroupSuccessAction(rolloutGroupRepository, deploymentManagement);
}
@Bean
@@ -260,8 +250,8 @@ public class JpaRepositoryConfiguration {
@Bean
@ConditionalOnMissingBean
SystemManagementCacheKeyGenerator systemManagementCacheKeyGenerator(final TenantAware tenantAware) {
return new SystemManagementCacheKeyGenerator(tenantAware);
SystemManagementCacheKeyGenerator systemManagementCacheKeyGenerator() {
return new SystemManagementCacheKeyGenerator();
}
@Bean
@@ -270,10 +260,10 @@ public class JpaRepositoryConfiguration {
return new PropertiesQuotaManagement(securityProperties);
}
// register as bean in order to be registered event listeners
@Bean
@ConditionalOnMissingBean
RolloutStatusCache rolloutStatusCache(final TenantAware tenantAware) {
return new RolloutStatusCache(tenantAware);
RolloutStatusCache rolloutStatusCache() {
return new RolloutStatusCache();
}
@Bean
@@ -282,46 +272,6 @@ public class JpaRepositoryConfiguration {
return e -> e instanceof TargetPollEvent && !repositoryProperties.isPublishTargetPollEvent();
}
/**
* @return the {@link SystemSecurityContext} singleton bean which make it
* accessible in beans which cannot access the service directly, e.g.
* JPA entities.
*/
@Bean
SystemSecurityContextHolder systemSecurityContextHolder() {
return SystemSecurityContextHolder.getInstance();
}
/**
* @return the {@link TenantConfigurationManagement} singleton bean which make
* it accessible in beans which cannot access the service directly, e.g.
* JPA entities.
*/
@Bean
TenantConfigurationManagementHolder tenantConfigurationManagementHolder() {
return TenantConfigurationManagementHolder.getInstance();
}
/**
* @return the {@link TenantAwareHolder} singleton bean which holds the current
* {@link TenantAware} service and make it accessible in beans which
* cannot access the service directly, e.g. JPA entities.
*/
@Bean
TenantAwareHolder tenantAwareHolder() {
return TenantAwareHolder.getInstance();
}
/**
* @return the {@link SecurityTokenGeneratorHolder} singleton bean which holds
* the current {@link SecurityTokenGenerator} service and make it
* accessible in beans which cannot access the service via injection
*/
@Bean
SecurityTokenGeneratorHolder securityTokenGeneratorHolder() {
return SecurityTokenGeneratorHolder.getInstance();
}
/**
* @return the singleton instance of the {@link EntityInterceptorHolder}
*/
@@ -340,10 +290,10 @@ public class JpaRepositoryConfiguration {
@Bean
@ConditionalOnMissingBean
RolloutHandler rolloutHandler(final TenantAware tenantAware, final RolloutManagement rolloutManagement,
RolloutHandler rolloutHandler(final RolloutManagement rolloutManagement,
final RolloutExecutor rolloutExecutor, final LockRegistry lockRegistry,
final PlatformTransactionManager txManager, final Optional<MeterRegistry> meterRegistry) {
return new JpaRolloutHandler(tenantAware, rolloutManagement, rolloutExecutor, lockRegistry, txManager, meterRegistry);
return new JpaRolloutHandler(rolloutManagement, rolloutExecutor, lockRegistry, txManager, meterRegistry);
}
/**
@@ -353,9 +303,8 @@ public class JpaRepositoryConfiguration {
*/
@Bean
@ConditionalOnMissingBean
RolloutApprovalStrategy rolloutApprovalStrategy(
final TenantConfigurationManagement tenantConfigurationManagement, final SystemSecurityContext systemSecurityContext) {
return new DefaultRolloutApprovalStrategy(tenantConfigurationManagement, systemSecurityContext);
RolloutApprovalStrategy rolloutApprovalStrategy() {
return new DefaultRolloutApprovalStrategy();
}
/**
@@ -372,44 +321,36 @@ public class JpaRepositoryConfiguration {
/**
* {@link EventEntityManager} bean.
*
* @param aware the tenant aware
* @param entityManager the entity manager
* @return a new {@link EventEntityManager}
*/
@Bean
@ConditionalOnMissingBean
EventEntityManager eventEntityManager(final TenantAware aware, final EntityManager entityManager) {
return new JpaEventEntityManager(aware, entityManager);
EventEntityManager eventEntityManager(final EntityManager entityManager) {
return new JpaEventEntityManager(entityManager);
}
/**
* {@link AutoAssignScheduler} bean.
* <p/>
* Note: does not activate in test profile, otherwise it is hard to test the
* auto assign functionality.
*
* @param systemManagement to find all tenants
* @param systemSecurityContext to run as system
* @param autoAssignExecutor to run a check as tenant
* @param lockRegistry to lock the tenant for auto assignment
* @return a new {@link JpaAutoAssignExecutor}
* Note: does not activate in test profile, otherwise it is hard to test the auto assign functionality.
*/
@Bean
@ConditionalOnMissingBean
// don't active the auto assign scheduler in test, otherwise it is hard to test
@Profile("!test")
@ConditionalOnProperty(prefix = "hawkbit.autoassign.scheduler", name = "enabled", matchIfMissing = true)
AutoAssignScheduler autoAssignScheduler(final SystemManagement systemManagement,
final SystemSecurityContext systemSecurityContext, final AutoAssignExecutor autoAssignExecutor,
AutoAssignScheduler autoAssignScheduler(
final SystemManagement systemManagement, final AutoAssignExecutor autoAssignExecutor,
final LockRegistry lockRegistry, final Optional<MeterRegistry> meterRegistry) {
return new AutoAssignScheduler(systemManagement, systemSecurityContext, autoAssignExecutor, lockRegistry, meterRegistry);
return new AutoAssignScheduler(systemManagement, autoAssignExecutor, lockRegistry, meterRegistry);
}
/**
* {@link AutoActionCleanup} bean.
*
* @param deploymentManagement Deployment management service
* @param configManagement Tenant configuration service
* @param configManagement AccessContext configuration service
* @return a new {@link AutoActionCleanup} bean
*/
@Bean
@@ -418,44 +359,29 @@ public class JpaRepositoryConfiguration {
return new AutoActionCleanup(deploymentManagement, configManagement);
}
/**
* {@link AutoCleanupScheduler} bean.
*
* @param systemManagement to find all tenants
* @param systemSecurityContext to run as system
* @param lockRegistry to lock the tenant for auto assignment
* @param cleanupTasks a list of cleanup tasks
* @return a new {@link AutoCleanupScheduler} bean
*/
@Bean
@ConditionalOnMissingBean
@Profile("!test")
@ConditionalOnProperty(prefix = "hawkbit.autocleanup.scheduler", name = "enabled", matchIfMissing = true)
AutoCleanupScheduler autoCleanupScheduler(
final List<AutoCleanupScheduler.CleanupTask> cleanupTasks,
final SystemManagement systemManagement, final SystemSecurityContext systemSecurityContext, final LockRegistry lockRegistry) {
return new AutoCleanupScheduler(cleanupTasks, systemManagement, systemSecurityContext, lockRegistry);
final SystemManagement systemManagement, final LockRegistry lockRegistry) {
return new AutoCleanupScheduler(cleanupTasks, systemManagement, lockRegistry);
}
/**
* {@link RolloutScheduler} bean.
* <p/>
* Note: does not activate in test profile, otherwise it is hard to test the
* rollout handling functionality.
*
* @param systemManagement to find all tenants
* @param rolloutHandler to run the rollout handler
* @param systemSecurityContext to run as system
* @return a new {@link RolloutScheduler} bean.
* Note: does not activate in test profile, otherwise it is hard to test the rollout handling functionality.
*/
@Bean
@ConditionalOnMissingBean
@Profile("!test")
@ConditionalOnProperty(prefix = "hawkbit.rollout.scheduler", name = "enabled", matchIfMissing = true)
RolloutScheduler rolloutScheduler(
final SystemManagement systemManagement, final RolloutHandler rolloutHandler, final SystemSecurityContext systemSecurityContext,
final SystemManagement systemManagement, final RolloutHandler rolloutHandler,
@Value("${hawkbit.rollout.executor.thread-pool.size:1}") final int threadPoolSize, final Optional<MeterRegistry> meterRegistry) {
return new RolloutScheduler(rolloutHandler, systemManagement, systemSecurityContext, threadPoolSize, meterRegistry);
return new RolloutScheduler(rolloutHandler, systemManagement, threadPoolSize, meterRegistry);
}
@Bean

View File

@@ -15,7 +15,8 @@ import java.util.Optional;
import jakarta.validation.constraints.NotNull;
import org.eclipse.hawkbit.tenancy.TenantAware;
import lombok.NonNull;
import org.eclipse.hawkbit.context.AccessContext;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.interceptor.SimpleKeyGenerator;
import org.springframework.context.annotation.Bean;
@@ -26,11 +27,6 @@ import org.springframework.context.annotation.Bean;
public class SystemManagementCacheKeyGenerator implements CurrentTenantCacheKeyGenerator {
private final ThreadLocal<String> createInitialTenant = new ThreadLocal<>();
private final TenantAware tenantAware;
public SystemManagementCacheKeyGenerator(final TenantAware tenantAware) {
this.tenantAware = tenantAware;
}
@Override
@Bean
@@ -70,16 +66,19 @@ public class SystemManagementCacheKeyGenerator implements CurrentTenantCacheKeyG
/**
* An implementation of the {@link KeyGenerator} to generate a key based on
* either the {@code createInitialTenant} thread local and the
* {@link TenantAware}, but in case we are in a tenant creation with its default
* {@link AccessContext}, but in case we are in a tenant creation with its default
* types we need to use as the tenant the current tenant which is currently
* created and not the one currently in the {@link TenantAware}.
* created and not the one currently in the {@link AccessContext}.
*/
public class CurrentTenantKeyGenerator implements KeyGenerator {
@Override
public Object generate(final Object target, final Method method, final Object... params) {
String tenant = getTenantInCreation().orElseGet(tenantAware::getCurrentTenant).toUpperCase();
public Object generate(@NonNull final Object target, @NonNull final Method method, @NonNull final Object... params) {
final String tenant = Objects.requireNonNull(
getTenantInCreation().orElseGet(AccessContext::tenant),
"CurrentTenantKeyGenerator.generate called not in tenant context")
.toUpperCase();
return SimpleKeyGenerator.generateKey(tenant, tenant);
}
}
}
}

View File

@@ -10,8 +10,9 @@
package org.eclipse.hawkbit.repository.jpa;
import java.lang.reflect.Method;
import java.util.Objects;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.context.AccessContext;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.stereotype.Service;
@@ -21,14 +22,11 @@ import org.springframework.stereotype.Service;
@Service
public class TenantKeyGenerator implements KeyGenerator {
private final TenantAware tenantAware;
public TenantKeyGenerator(final TenantAware tenantAware) {
this.tenantAware = tenantAware;
}
@Override
public Object generate(final Object target, final Method method, final Object... params) {
return tenantAware.getCurrentTenant().toUpperCase();
return Objects.requireNonNull(
AccessContext.tenant(),
"TenantKeyGenerator.generate called not in tenant context")
.toUpperCase();
}
}

View File

@@ -21,13 +21,7 @@ import jakarta.persistence.criteria.Root;
import jakarta.persistence.metamodel.EntityType;
import org.aopalliance.intercept.MethodInvocation;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.repository.qfields.DistributionSetFields;
import org.eclipse.hawkbit.repository.qfields.DistributionSetTypeFields;
import org.eclipse.hawkbit.repository.qfields.SoftwareModuleFields;
import org.eclipse.hawkbit.repository.qfields.SoftwareModuleTypeFields;
import org.eclipse.hawkbit.repository.qfields.TargetFields;
import org.eclipse.hawkbit.repository.qfields.TargetTypeFields;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction_;
@@ -37,8 +31,12 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetType;
import org.eclipse.hawkbit.security.SecurityContextSerializer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.eclipse.hawkbit.repository.qfields.DistributionSetFields;
import org.eclipse.hawkbit.repository.qfields.DistributionSetTypeFields;
import org.eclipse.hawkbit.repository.qfields.SoftwareModuleFields;
import org.eclipse.hawkbit.repository.qfields.SoftwareModuleTypeFields;
import org.eclipse.hawkbit.repository.qfields.TargetFields;
import org.eclipse.hawkbit.repository.qfields.TargetTypeFields;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -60,12 +58,6 @@ import org.springframework.util.function.SingletonSupplier;
@ConditionalOnProperty(name = "hawkbit.acm.access-controller.enabled", havingValue = "true")
public class AccessControllerConfiguration {
@Bean
@ConditionalOnMissingBean
SecurityContextSerializer securityContextSerializer() {
return SecurityContextSerializer.JSON_SERIALIZATION;
}
@Bean
@ConditionalOnProperty(name = "hawkbit.acm.access-controller.target.enabled", havingValue = "true", matchIfMissing = true)
AccessController<JpaTarget> targetAccessController() {

View File

@@ -13,7 +13,7 @@ import java.util.Set;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.ql.jpa.QLSupport;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModule;

View File

@@ -18,10 +18,10 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.ql.QueryField;
import org.eclipse.hawkbit.ql.jpa.QLSupport;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -48,7 +48,7 @@ public class DefaultAccessController<A extends Enum<A> & QueryField, T> implemen
@Override
public Optional<Specification<T>> getAccessRules(final Operation operation) {
if (SystemSecurityContext.isCurrentThreadSystemCode()) {
if (AccessContext.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();
}
@@ -63,7 +63,7 @@ public class DefaultAccessController<A extends Enum<A> & QueryField, T> implemen
@Override
public void assertOperationAllowed(final Operation operation, final T entity) throws InsufficientPermissionException {
if (SystemSecurityContext.isCurrentThreadSystemCode()) {
if (AccessContext.isCurrentThreadSystemCode()) {
// system code - no restrictions. this runs with SYSTEM_ROLE, so no restrictions apply anyway - not scopes, but this way should be faster
return;
}

View File

@@ -9,12 +9,13 @@
*/
package org.eclipse.hawkbit.repository.jpa.autocleanup;
import static org.eclipse.hawkbit.context.AccessContext.asSystem;
import java.util.List;
import java.util.concurrent.locks.Lock;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.springframework.integration.support.locks.LockRegistry;
import org.springframework.scheduling.annotation.Scheduled;
@@ -29,7 +30,6 @@ public class AutoCleanupScheduler {
private static final String PROP_AUTO_CLEANUP_INTERVAL = "${hawkbit.autocleanup.scheduler.fixedDelay:86400000}";
private final SystemManagement systemManagement;
private final SystemSecurityContext systemSecurityContext;
private final LockRegistry lockRegistry;
private final List<CleanupTask> cleanupTasks;
@@ -38,14 +38,12 @@ public class AutoCleanupScheduler {
*
* @param cleanupTasks A list of cleanup tasks.
* @param systemManagement Management APIs to invoke actions in a certain tenant context.
* @param systemSecurityContext The system security context.
* @param lockRegistry A registry for shared locks.
*/
public AutoCleanupScheduler(
final List<CleanupTask> cleanupTasks,
final SystemManagement systemManagement, final SystemSecurityContext systemSecurityContext, final LockRegistry lockRegistry) {
final SystemManagement systemManagement, final LockRegistry lockRegistry) {
this.systemManagement = systemManagement;
this.systemSecurityContext = systemSecurityContext;
this.lockRegistry = lockRegistry;
this.cleanupTasks = cleanupTasks;
}
@@ -58,7 +56,7 @@ public class AutoCleanupScheduler {
log.debug("Auto cleanup scheduler has been triggered.");
// run this code in system code privileged to have the necessary permission to query and create entities
if (!cleanupTasks.isEmpty()) {
systemSecurityContext.runAsSystem(this::executeAutoCleanup);
asSystem(this::executeAutoCleanup);
}
}

View File

@@ -9,11 +9,12 @@
*/
package org.eclipse.hawkbit.repository.jpa.event;
import static org.eclipse.hawkbit.context.AccessContext.asSystemAsTenant;
import jakarta.persistence.EntityManager;
import org.eclipse.hawkbit.repository.event.remote.EventEntityManager;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.transaction.annotation.Transactional;
/**
@@ -22,22 +23,19 @@ import org.springframework.transaction.annotation.Transactional;
@Transactional(readOnly = true)
public class JpaEventEntityManager implements EventEntityManager {
private final TenantAware tenantAware;
private final EntityManager entityManager;
/**
* Constructor.
*
* @param tenantAware the tenant aware
* @param entityManager the entity manager
*/
public JpaEventEntityManager(final TenantAware tenantAware, final EntityManager entityManager) {
this.tenantAware = tenantAware;
public JpaEventEntityManager(final EntityManager entityManager) {
this.entityManager = entityManager;
}
@Override
public <E extends TenantAwareBaseEntity> E findEntity(final String tenant, final Long id, final Class<E> entityType) {
return tenantAware.runAsTenant(tenant, () -> entityManager.find(entityType, id));
return asSystemAsTenant(tenant, () -> entityManager.find(entityType, id));
}
}

View File

@@ -21,6 +21,7 @@ import java.util.function.Consumer;
import jakarta.persistence.criteria.JoinType;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.RepositoryProperties;
@@ -87,7 +88,7 @@ public abstract class AbstractDsAssignmentStrategy {
}
public JpaAction createTargetAction(
final String initiatedBy, final TargetWithActionType targetWithActionType,
final TargetWithActionType targetWithActionType,
final List<JpaTarget> targets, final JpaDistributionSet set) {
final Optional<JpaTarget> optTarget = targets.stream()
.filter(t -> t.getControllerId().equals(targetWithActionType.getControllerId())).findFirst();
@@ -108,7 +109,7 @@ public abstract class AbstractDsAssignmentStrategy {
actionForTarget.setMaintenanceWindowSchedule(targetWithActionType.getMaintenanceSchedule());
actionForTarget.setMaintenanceWindowDuration(targetWithActionType.getMaintenanceWindowDuration());
actionForTarget.setMaintenanceWindowTimeZone(targetWithActionType.getMaintenanceWindowTimeZone());
actionForTarget.setInitiatedBy(initiatedBy);
actionForTarget.setInitiatedBy(AccessContext.actor());
return actionForTarget;
}).orElseGet(() -> {
log.warn("Cannot find target for targetWithActionType '{}'.", targetWithActionType.getControllerId());
@@ -234,10 +235,8 @@ public abstract class AbstractDsAssignmentStrategy {
*
* @param distributionSet to set
* @param targetIds to change
* @param currentUser for auditing
*/
abstract void setAssignedDistributionSetAndTargetStatus(final JpaDistributionSet distributionSet,
final List<List<Long>> targetIds, final String currentUser);
abstract void setAssignedDistributionSetAndTargetStatus(final JpaDistributionSet distributionSet, final List<List<Long>> targetIds);
/**
* Cancels actions that can be canceled (i.e.
@@ -265,8 +264,7 @@ public abstract class AbstractDsAssignmentStrategy {
final RolloutGroup rolloutGroup = action.getRolloutGroup();
if (rolloutGroup != null) {
final Rollout rollout = rolloutGroup.getRollout();
return String.format("Initiated by Rollout Group '%s' [Rollout %s:%s]", rolloutGroup.getName(),
rollout.getName(), rollout.getId());
return String.format("Initiated by Rollout Group '%s' [Rollout %s:%s]", rolloutGroup.getName(), rollout.getName(), rollout.getId());
}
return String.format("Assignment initiated by user '%s'", action.getInitiatedBy());
}

View File

@@ -28,6 +28,7 @@ import org.eclipse.hawkbit.artifact.exception.HashNotMatchException;
import org.eclipse.hawkbit.artifact.model.ArtifactHashes;
import org.eclipse.hawkbit.artifact.model.ArtifactStream;
import org.eclipse.hawkbit.artifact.model.StoredArtifactInfo;
import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
@@ -49,7 +50,6 @@ import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.ArtifactUpload;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.retry.annotation.Backoff;
@@ -74,7 +74,6 @@ public class JpaArtifactManagement implements ArtifactManagement {
private final SoftwareModuleRepository softwareModuleRepository;
private final EntityManager entityManager;
private final PlatformTransactionManager txManager;
private final TenantAware tenantAware;
private final QuotaManagement quotaManagement;
protected JpaArtifactManagement(
@@ -83,15 +82,13 @@ public class JpaArtifactManagement implements ArtifactManagement {
final SoftwareModuleRepository softwareModuleRepository,
final EntityManager entityManager,
final PlatformTransactionManager txManager,
final QuotaManagement quotaManagement,
final TenantAware tenantAware) {
final QuotaManagement quotaManagement) {
this.artifactRepository = artifactRepository;
this.artifactStorage = artifactStorage.orElse(null);
this.softwareModuleRepository = softwareModuleRepository;
this.entityManager = entityManager;
this.txManager = txManager;
this.quotaManagement = quotaManagement;
this.tenantAware = tenantAware;
}
@Override
@@ -135,7 +132,7 @@ public class JpaArtifactManagement implements ArtifactManagement {
try {
return storeArtifactMetadata(softwareModule, filename, artifact.getHashes(), artifact.getSize(), existing);
} catch (final Exception e) {
artifactStorage.deleteBySha1(tenantAware.getCurrentTenant(), artifact.getHashes().sha1());
artifactStorage.deleteBySha1(AccessContext.tenant(), artifact.getHashes().sha1());
throw e;
}
}
@@ -147,7 +144,7 @@ public class JpaArtifactManagement implements ArtifactManagement {
throw new UnsupportedOperationException();
}
final String tenant = tenantAware.getCurrentTenant();
final String tenant = AccessContext.tenant();
// check access to the software module and if artifact belongs to it
for (final Artifact artifact : softwareModuleRepository.getById(softwareModuleId).getArtifacts()) {
if (artifact.getSha1Hash().equals(sha1Hash)) {
@@ -203,13 +200,13 @@ public class JpaArtifactManagement implements ArtifactManagement {
void clearArtifactBinary(final String sha1Hash) {
DeploymentHelper.runInNewTransaction(txManager, "clearArtifactBinary", status -> {
// countBySha1HashAndTenantAndSoftwareModuleDeletedIsFalse will skip ACM checks and will return total count as it should be
if (artifactRepository.countBySha1HashAndTenantAndSoftwareModuleDeletedIsFalse(sha1Hash, tenantAware.getCurrentTenant()) <= 0) {
if (artifactRepository.countBySha1HashAndTenantAndSoftwareModuleDeletedIsFalse(sha1Hash, AccessContext.tenant()) <= 0) {
// removes the real artifact ONLY AFTER the delete of artifact or software module
// in local history has passed successfully (caller has permission and no errors)
afterCommit(() -> {
try {
log.debug("deleting artifact from repository {}", sha1Hash);
artifactStorage.deleteBySha1(tenantAware.getCurrentTenant(), sha1Hash);
artifactStorage.deleteBySha1(AccessContext.tenant(), sha1Hash);
} catch (final ArtifactStoreException e) {
throw new ArtifactDeleteFailedException(e);
}
@@ -224,7 +221,7 @@ public class JpaArtifactManagement implements ArtifactManagement {
try (final InputStream wrappedStream = wrapInQuotaStream(
isSmEncrypted ? ArtifactEncryptionService.getInstance().encryptArtifact(artifactUpload.moduleId(), stream) : stream)) {
return artifactStorage.store(
tenantAware.getCurrentTenant(),
AccessContext.tenant(),
wrappedStream, artifactUpload.filename(),
artifactUpload.contentType(), artifactUpload.hash());
} catch (final ArtifactStoreException | IOException e) {

View File

@@ -22,7 +22,6 @@ import jakarta.persistence.EntityManager;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.ConfirmationManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.exception.AutoConfirmationAlreadyActiveException;
import org.eclipse.hawkbit.repository.exception.InvalidConfirmationFeedbackException;

View File

@@ -9,6 +9,9 @@
*/
package org.eclipse.hawkbit.repository.jpa.management;
import static org.eclipse.hawkbit.context.AccessContext.asActor;
import static org.eclipse.hawkbit.context.AccessContext.asSystem;
import static org.eclipse.hawkbit.context.AccessContext.asSystemAsTenant;
import static org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor.afterCommit;
import static org.eclipse.hawkbit.repository.model.Action.Status.DOWNLOADED;
import static org.eclipse.hawkbit.repository.model.Action.Status.FINISHED;
@@ -46,6 +49,7 @@ import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.ListUtils;
import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.ql.jpa.QLSupport;
import org.eclipse.hawkbit.repository.ConfirmationManagement;
import org.eclipse.hawkbit.repository.ControllerManagement;
@@ -55,9 +59,8 @@ import org.eclipse.hawkbit.repository.MaintenanceScheduleHelper;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.SecurityTokenGeneratorHolder;
import org.eclipse.hawkbit.repository.SecurityTokenGenerator;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.UpdateMode;
import org.eclipse.hawkbit.repository.event.EventPublisherHolder;
import org.eclipse.hawkbit.repository.event.remote.CancelTargetAssignmentEvent;
@@ -68,6 +71,7 @@ import org.eclipse.hawkbit.repository.exception.EntityAlreadyExistsException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.InvalidTargetAttributeException;
import org.eclipse.hawkbit.repository.exception.SoftwareModuleNotAssignedToTargetException;
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
import org.eclipse.hawkbit.repository.jpa.Jpa;
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
@@ -101,13 +105,11 @@ import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.qfields.TargetFields;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.configuration.ControllerPollProperties;
import org.eclipse.hawkbit.tenancy.configuration.DurationHelper;
import org.eclipse.hawkbit.tenancy.configuration.PollingTime;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.eclipse.hawkbit.util.IpUtil;
import org.eclipse.hawkbit.utils.IpUtil;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.data.domain.Page;
@@ -137,8 +139,6 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
private static final Pattern PATTERN = Pattern.compile("[a-zA-Z0-9_\\-!@#$%^&*()+=\\[\\]{}|;:'\",.<>/\\\\?\\s]*");
private final BlockingDeque<TargetPoll> queue;
// TODO - make it final
private TargetRepository targetRepository;
private final TargetTypeRepository targetTypeRepository;
@@ -147,14 +147,13 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
private final SoftwareModuleRepository softwareModuleRepository;
private final SoftwareModuleManagement<? extends SoftwareModule> softwareModuleManagement;
private final DistributionSetManagement<? extends DistributionSet> distributionSetManagement;
private final TenantConfigurationManagement tenantConfigurationManagement;
private final ControllerPollProperties controllerPollProperties;
private final Duration minPollingTime;
private final Duration maxPollingTime;
private final PlatformTransactionManager txManager;
private final EntityManager entityManager;
private final SystemSecurityContext systemSecurityContext;
private final TenantAware tenantAware;
private final Duration minPollingTime;
private final Duration maxPollingTime;
private final BlockingDeque<TargetPoll> queue;
@SuppressWarnings("squid:S00107")
protected JpaControllerManagement(
@@ -165,9 +164,8 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
final SoftwareModuleRepository softwareModuleRepository,
final SoftwareModuleManagement<? extends SoftwareModule> softwareModuleManagement,
final DistributionSetManagement<? extends DistributionSet> distributionSetManagement,
final TenantConfigurationManagement tenantConfigurationManagement, final ControllerPollProperties controllerPollProperties,
final ControllerPollProperties controllerPollProperties,
final PlatformTransactionManager txManager, final EntityManager entityManager,
final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware,
final ScheduledExecutorService executorService) {
super(actionRepository, actionStatusRepository, quotaManagement, repositoryProperties);
@@ -178,19 +176,16 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
this.softwareModuleRepository = softwareModuleRepository;
this.softwareModuleManagement = softwareModuleManagement;
this.distributionSetManagement = distributionSetManagement;
this.tenantConfigurationManagement = tenantConfigurationManagement;
this.controllerPollProperties = controllerPollProperties;
this.txManager = txManager;
this.entityManager = entityManager;
minPollingTime = controllerPollProperties.getMinPollingTime() == null
? Duration.of(0, ChronoUnit.SECONDS)
: DurationHelper.fromString(controllerPollProperties.getMinPollingTime());
maxPollingTime = controllerPollProperties.getMaxPollingTime() == null
? Duration.of(100, ChronoUnit.YEARS)
: DurationHelper.fromString(controllerPollProperties.getMaxPollingTime());
this.txManager = txManager;
this.entityManager = entityManager;
this.systemSecurityContext = systemSecurityContext;
this.tenantAware = tenantAware;
if (!repositoryProperties.isEagerPollPersistence()) {
executorService.scheduleWithFixedDelay(this::flushUpdateQueue,
repositoryProperties.getPollPersistenceFlushTime(),
@@ -277,7 +272,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
@Override
public Map<Long, Map<String, String>> findTargetVisibleMetaDataBySoftwareModuleId(final Collection<Long> moduleId) {
return systemSecurityContext.runAsSystem(() -> softwareModuleManagement.findMetaDataBySoftwareModuleIdsAndTargetVisible(moduleId));
return asSystem(() -> softwareModuleManagement.findMetaDataBySoftwareModuleIdsAndTargetVisible(moduleId));
}
@Override
@@ -383,9 +378,11 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
@Override
public String getPollingTime(final Target target) {
return systemSecurityContext.runAsSystem(() -> {
// as system so to be able to read tenant configuration (READ_TENANT_CONFIGURATION)
return asSystem(() -> {
final PollingTime pollingTime = new PollingTime(
tenantConfigurationManagement.getConfigurationValue(TenantConfigurationKey.POLLING_TIME, String.class).getValue());
TenantConfigHelper.getTenantConfigurationManagement()
.getConfigurationValue(TenantConfigurationKey.POLLING_TIME, String.class).getValue());
if (!ObjectUtils.isEmpty(pollingTime.getOverrides()) && target instanceof JpaTarget jpaTarget) {
for (final PollingTime.Override override : pollingTime.getOverrides()) {
try {
@@ -409,9 +406,8 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
return pollingTime;
} else {
// the count to be used for reducing polling interval -> the configured value of {@link TenantConfigurationKey#MAINTENANCE_WINDOW_POLL_COUNT}
final int maintenanceWindowPollCount = systemSecurityContext.runAsSystem(
() -> tenantConfigurationManagement.getConfigurationValue(
TenantConfigurationKey.MAINTENANCE_WINDOW_POLL_COUNT, Integer.class).getValue());
final int maintenanceWindowPollCount = TenantConfigHelper.getAsSystem(
TenantConfigurationKey.MAINTENANCE_WINDOW_POLL_COUNT, Integer.class);
return new EventTimer(pollingTime, controllerPollProperties.getMinPollingTime(), ChronoUnit.SECONDS)
.timeToNextEvent(maintenanceWindowPollCount, action.getMaintenanceWindowStartTime().orElse(null));
}
@@ -524,7 +520,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
jpaAction.setStatus(Status.CANCELING);
// document that the status has been retrieved
actionStatusRepository.save(
new JpaActionStatus(jpaAction, Status.CANCELING, System.currentTimeMillis(), "manual cancelation requested"));
new JpaActionStatus(jpaAction, Status.CANCELING, java.lang.System.currentTimeMillis(), "manual cancelation requested"));
final Action saveAction = actionRepository.save(jpaAction);
cancelAssignDistributionSetEvent(jpaAction);
@@ -572,9 +568,8 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
@Override
public boolean updateOfflineAssignedVersion(@NotEmpty final String controllerId, final String distributionName, final String version) {
List<DistributionSetAssignmentResult> distributionSetAssignmentResults =
systemSecurityContext.runAsSystem(() -> deploymentManagement.offlineAssignedDistributionSets(
controllerId,
List.of(Map.entry(controllerId, distributionSetManagement.findByNameAndVersion(distributionName, version).getId()))));
asSystem(() -> asActor(controllerId, () -> deploymentManagement.offlineAssignedDistributionSets(
List.of(Map.entry(controllerId, distributionSetManagement.findByNameAndVersion(distributionName, version).getId())))));
return distributionSetAssignmentResults.stream()
.findFirst()
@@ -656,9 +651,9 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
jpaTarget.setControllerId(controllerId);
jpaTarget.setDescription("Plug and Play target: " + controllerId);
jpaTarget.setName((StringUtils.hasText(name) ? name : controllerId));
jpaTarget.setSecurityToken(SecurityTokenGeneratorHolder.getInstance().generateToken());
jpaTarget.setSecurityToken(SecurityTokenGenerator.generateToken());
jpaTarget.setUpdateStatus(TargetUpdateStatus.REGISTERED);
jpaTarget.setLastTargetQuery(System.currentTimeMillis());
jpaTarget.setLastTargetQuery(java.lang.System.currentTimeMillis());
jpaTarget.setAddress(Optional.ofNullable(address).map(URI::toString).orElse(null));
if (StringUtils.hasText(type)) {
@@ -701,8 +696,8 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
try {
events.stream().collect(Collectors.groupingBy(TargetPoll::getTenant)).forEach((tenant, polls) -> {
final TransactionCallback<Void> createTransaction = status -> updateLastTargetQueries(tenant, polls);
tenantAware.runAsTenant(tenant,
() -> DeploymentHelper.runInNewTransaction(txManager, "flushUpdateQueue", createTransaction));
asSystemAsTenant(
tenant, () -> DeploymentHelper.runInNewTransaction(txManager, "flushUpdateQueue", createTransaction));
});
} catch (final RuntimeException ex) {
log.error("Failed to persist UpdateQueue content.", ex);
@@ -720,7 +715,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
Constants.MAX_ENTRIES_IN_STATEMENT);
pollChunks.forEach(chunk -> {
setLastTargetQuery(tenant, System.currentTimeMillis(), chunk);
setLastTargetQuery(tenant, java.lang.System.currentTimeMillis(), chunk);
chunk.forEach(controllerId -> afterCommit(() -> EventPublisherHolder.getInstance().getEventPublisher()
.publishEvent(new TargetPollEvent(controllerId, tenant))));
});
@@ -781,7 +776,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
if (isStatusUnknown(toUpdate.getUpdateStatus())) {
toUpdate.setUpdateStatus(TargetUpdateStatus.REGISTERED);
}
toUpdate.setLastTargetQuery(System.currentTimeMillis());
toUpdate.setLastTargetQuery(java.lang.System.currentTimeMillis());
afterCommit(() -> EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new TargetPollEvent(toUpdate)));
return targetRepository.save(toUpdate);
}
@@ -827,7 +822,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
target.setRequestControllerAttributes(true);
EventPublisherHolder.getInstance().getEventPublisher()
.publishEvent(new TargetAttributesRequestedEvent(tenantAware.getCurrentTenant(), target.getId(),
.publishEvent(new TargetAttributesRequestedEvent(AccessContext.tenant(), target.getId(),
JpaTarget.class, target.getControllerId(), target.getAddress() != null ? target.getAddress() : null));
}
@@ -908,7 +903,7 @@ public class JpaControllerManagement extends JpaActionManagement implements Cont
// case controller retrieves a action multiple times.
if (resultList.isEmpty() || (Status.RETRIEVED != resultList.get(0)[1])) {
// document that the status has been retrieved
actionStatusRepository.save(new JpaActionStatus(action, Status.RETRIEVED, System.currentTimeMillis(), message));
actionStatusRepository.save(new JpaActionStatus(action, Status.RETRIEVED, java.lang.System.currentTimeMillis(), message));
// don't change the action status itself in case the action is in
// canceling state otherwise

View File

@@ -9,9 +9,9 @@
*/
package org.eclipse.hawkbit.repository.jpa.management;
import static org.eclipse.hawkbit.context.AccessContext.asSystem;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -38,12 +38,12 @@ import jakarta.persistence.criteria.Root;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.ListUtils;
import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.ql.jpa.QLSupport;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.event.remote.TargetAssignDistributionSetEvent;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.CancelActionNotAllowedException;
@@ -53,6 +53,7 @@ import org.eclipse.hawkbit.repository.exception.IncompatibleTargetTypeException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.repository.exception.MultiAssignmentIsNotEnabledException;
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
import org.eclipse.hawkbit.repository.jpa.Jpa;
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
import org.eclipse.hawkbit.repository.jpa.acm.AccessController;
@@ -86,14 +87,10 @@ import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
import org.eclipse.hawkbit.repository.model.TargetWithActionType;
import org.eclipse.hawkbit.repository.qfields.ActionFields;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.eclipse.hawkbit.utils.TenantConfigHelper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
@@ -132,34 +129,30 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
"DELETE FROM sp_action " + "WHERE id IN (SELECT id FROM sp_action " + "WHERE tenant=" + Jpa.nativeQueryParamPrefix() + "tenant" + " AND status IN (%s)" + " AND last_modified_at<" + Jpa.nativeQueryParamPrefix() + "last_modified_at LIMIT " + ACTION_PAGE_LIMIT + ")");
}
private final EntityManager entityManager;
private final JpaDistributionSetManagement distributionSetManagement;
private final TargetRepository targetRepository;
private final AuditorAware<String> auditorProvider;
private final EntityManager entityManager;
private final PlatformTransactionManager txManager;
private final Database database;
private final RetryTemplate retryTemplate;
private final OnlineDsAssignmentStrategy onlineDsAssignmentStrategy;
private final OfflineDsAssignmentStrategy offlineDsAssignmentStrategy;
private final TenantConfigurationManagement tenantConfigurationManagement;
private final SystemSecurityContext systemSecurityContext;
private final TenantAware tenantAware;
private final AuditorAware<String> auditorAware;
private final Database database;
private final RetryTemplate retryTemplate;
@SuppressWarnings("java:S107")
protected JpaDeploymentManagement(final EntityManager entityManager, final ActionRepository actionRepository,
protected JpaDeploymentManagement(
final ActionRepository actionRepository, final ActionStatusRepository actionStatusRepository,
final QuotaManagement quotaManagement, final RepositoryProperties repositoryProperties,
final JpaDistributionSetManagement distributionSetManagement, final TargetRepository targetRepository,
final ActionStatusRepository actionStatusRepository, final AuditorAware<String> auditorProvider,
final PlatformTransactionManager txManager,
final TenantConfigurationManagement tenantConfigurationManagement, final QuotaManagement quotaManagement,
final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware, final AuditorAware<String> auditorAware,
final JpaProperties jpaProperties, final RepositoryProperties repositoryProperties) {
final EntityManager entityManager, final PlatformTransactionManager txManager, final JpaProperties jpaProperties) {
super(actionRepository, actionStatusRepository, quotaManagement, repositoryProperties);
this.entityManager = entityManager;
this.distributionSetManagement = distributionSetManagement;
this.targetRepository = targetRepository;
this.auditorProvider = auditorProvider;
this.entityManager = entityManager;
this.txManager = txManager;
this.database = jpaProperties.getDatabase();
retryTemplate = createRetryTemplate();
final Consumer<MaxAssignmentsExceededInfo> maxAssignmentsExceededHandler = maxAssignmentsExceededInfo ->
handleMaxAssignmentsExceeded(
maxAssignmentsExceededInfo.targetId,
@@ -171,51 +164,26 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
offlineDsAssignmentStrategy = new OfflineDsAssignmentStrategy(targetRepository, actionRepository, actionStatusRepository,
quotaManagement, this::isMultiAssignmentsEnabled, this::isConfirmationFlowEnabled, repositoryProperties,
maxAssignmentsExceededHandler);
this.tenantConfigurationManagement = tenantConfigurationManagement;
this.systemSecurityContext = systemSecurityContext;
this.tenantAware = tenantAware;
this.auditorAware = auditorAware;
this.database = jpaProperties.getDatabase();
this.retryTemplate = createRetryTemplate();
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
public List<DistributionSetAssignmentResult> assignDistributionSets(final List<DeploymentRequest> deploymentRequests) {
return assignDistributionSets0(tenantAware.getCurrentUsername(), deploymentRequests, null);
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
public List<DistributionSetAssignmentResult> assignDistributionSets(
final String initiatedBy, final List<DeploymentRequest> deploymentRequests, final String actionMessage) {
return assignDistributionSets0(initiatedBy, deploymentRequests, actionMessage);
}
private List<DistributionSetAssignmentResult> assignDistributionSets0(
final String initiatedBy, final List<DeploymentRequest> deploymentRequests, final String actionMessage) {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(deploymentRequests);
return assignDistributionSets(initiatedBy, deploymentRequests, actionMessage, onlineDsAssignmentStrategy);
final List<DeploymentRequest> deploymentRequests, final String actionMessage) {
WeightValidationHelper.validate(deploymentRequests);
return assignDistributionSets(deploymentRequests, actionMessage, onlineDsAssignmentStrategy);
}
@Override
public List<DistributionSetAssignmentResult> offlineAssignedDistributionSets(
final String initiatedBy, final Collection<Entry<String, Long>> assignments) {
@Transactional(isolation = Isolation.READ_COMMITTED)
public List<DistributionSetAssignmentResult> offlineAssignedDistributionSets(final Collection<Entry<String, Long>> assignments) {
final Collection<Entry<String, Long>> distinctAssignments = assignments.stream().distinct().toList();
enforceMaxAssignmentsPerRequest(distinctAssignments.size());
final List<DeploymentRequest> deploymentRequests = distinctAssignments.stream()
.map(entry -> DeploymentRequest.builder(entry.getKey(), entry.getValue()).build()).toList();
return assignDistributionSets(
auditorAware.getCurrentAuditor().orElse(tenantAware.getCurrentUsername()), deploymentRequests, null,
offlineDsAssignmentStrategy);
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED)
public List<DistributionSetAssignmentResult> offlineAssignedDistributionSets(final Collection<Entry<String, Long>> assignments) {
return offlineAssignedDistributionSets(tenantAware.getCurrentUsername(), assignments);
return assignDistributionSets(deploymentRequests, null, offlineDsAssignmentStrategy);
}
@Override
@@ -242,7 +210,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
action.setStatus(Status.CANCELING);
// document that the status has been retrieved
actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELING, System.currentTimeMillis(),
actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELING, java.lang.System.currentTimeMillis(),
RepositoryConstants.SERVER_MESSAGE_PREFIX + "manual cancelation requested"));
final Action saveAction = actionRepository.save(action);
@@ -384,7 +352,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
log.warn("action ({}) was still active and has been force quite.", action);
// document that the status has been retrieved
actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELED, System.currentTimeMillis(),
actionStatusRepository.save(new JpaActionStatus(action, Status.CANCELED, java.lang.System.currentTimeMillis(),
RepositoryConstants.SERVER_MESSAGE_PREFIX + "A force quit has been performed."));
DeploymentHelper.successCancellation(action, actionRepository, targetRepository);
@@ -533,7 +501,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
String.format(getQueryForDeleteActionsByStatusAndLastModifiedBeforeString(database),
Jpa.formatNativeQueryInClause("status", statusList)));
deleteQuery.setParameter("tenant", tenantAware.getCurrentTenant().toUpperCase());
deleteQuery.setParameter("tenant", AccessContext.tenant().toUpperCase());
Jpa.setNativeQueryInParameter(deleteQuery, "status", statusList);
deleteQuery.setParameter("last_modified_at", lastModified);
@@ -603,8 +571,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
}
}
;
/**
* Deletes the first n target actions of a target
*
@@ -628,11 +594,11 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
}
private int getActionsPurgePercentage() {
return getConfigValue(TenantConfigurationKey.ACTION_CLEANUP_ON_QUOTA_HIT_PERCENTAGE, Integer.class);
return TenantConfigHelper.getAsSystem(TenantConfigurationKey.ACTION_CLEANUP_ON_QUOTA_HIT_PERCENTAGE, Integer.class);
}
protected boolean isActionsAutocloseEnabled() {
return getConfigValue(REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED, Boolean.class);
return TenantConfigHelper.getAsSystem(REPOSITORY_ACTIONS_AUTOCLOSE_ENABLED, Boolean.class);
}
private static Map<Long, List<TargetWithActionType>> convertRequest(final Collection<DeploymentRequest> deploymentRequests) {
@@ -667,21 +633,20 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
backOffPolicy.setBackOffPeriod(Constants.TX_RT_DELAY);
template.setBackOffPolicy(backOffPolicy);
final SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(Constants.TX_RT_MAX,
Collections.singletonMap(ConcurrencyFailureException.class, true));
final SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(
Constants.TX_RT_MAX, Collections.singletonMap(ConcurrencyFailureException.class, true));
template.setRetryPolicy(retryPolicy);
return template;
}
private List<DistributionSetAssignmentResult> assignDistributionSets(
final String initiatedBy, final List<DeploymentRequest> deploymentRequests, final String actionMessage,
final AbstractDsAssignmentStrategy strategy) {
final List<DeploymentRequest> deploymentRequests, final String actionMessage, final AbstractDsAssignmentStrategy strategy) {
final List<DeploymentRequest> validatedRequests = validateAndFilterRequestForAssignments(deploymentRequests);
final Map<Long, List<TargetWithActionType>> assignmentsByDsIds = convertRequest(validatedRequests);
final List<DistributionSetAssignmentResult> results = assignmentsByDsIds.entrySet().stream()
.map(entry -> assignDistributionSetToTargetsWithRetry(initiatedBy, entry.getKey(), entry.getValue(), actionMessage, strategy))
.map(entry -> assignDistributionSetToTargetsWithRetry(entry.getKey(), entry.getValue(), actionMessage, strategy))
.toList();
strategy.sendDeploymentEvents(results);
return results;
@@ -774,10 +739,10 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
}
private DistributionSetAssignmentResult assignDistributionSetToTargetsWithRetry(
final String initiatedBy, final Long dsId, final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
final Long dsId, final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
final AbstractDsAssignmentStrategy assignmentStrategy) {
return retryTemplate.execute(retryContext ->
assignDistributionSetToTargets(initiatedBy, dsId, targetsWithActionType, actionMessage, assignmentStrategy));
assignDistributionSetToTargets(dsId, targetsWithActionType, actionMessage, assignmentStrategy));
}
/**
@@ -793,7 +758,6 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
* to {@link TargetUpdateStatus#IN_SYNC} <br/>
* D. does not send a {@link TargetAssignDistributionSetEvent}.<br/>
*
* @param initiatedBy the username of the user who initiated the assignment
* @param dsId the ID of the distribution set to assign
* @param targetsWithActionType a list of all targets and their action type
* @param actionMessage an optional message to be written into the action status
@@ -803,7 +767,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
* {@link DistributionSetType}.
*/
private DistributionSetAssignmentResult assignDistributionSetToTargets(
final String initiatedBy, final Long dsId, final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
final Long dsId, final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
final AbstractDsAssignmentStrategy assignmentStrategy) {
final JpaDistributionSet dsValidAndComplete = distributionSetManagement.getValidAndComplete(dsId);
final JpaDistributionSet distributionSet;
@@ -837,7 +801,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
.filter(target -> existingTargetIds.contains(target.getControllerId())).toList();
final List<JpaAction> assignedActions = doAssignDistributionSetToTargets(
initiatedBy, existingTargetsWithActionType, actionMessage, assignmentStrategy, distributionSet, targetEntities);
existingTargetsWithActionType, actionMessage, assignmentStrategy, distributionSet, targetEntities);
return buildAssignmentResult(distributionSet, assignedActions, existingTargetsWithActionType.size());
}
@@ -849,7 +813,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
return new DistributionSetAssignmentResult(distributionSetEntity, alreadyAssignedCount, Collections.emptyList());
}
private List<JpaAction> doAssignDistributionSetToTargets(final String initiatedBy,
private List<JpaAction> doAssignDistributionSetToTargets(
final Collection<TargetWithActionType> targetsWithActionType, final String actionMessage,
final AbstractDsAssignmentStrategy assignmentStrategy, final JpaDistributionSet distributionSetEntity,
final List<JpaTarget> targetEntities) {
@@ -863,7 +827,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
targetEntitiesIdsChunks.forEach(this::cancelInactiveScheduledActionsForTargets);
setAssignedDistributionSetAndTargetUpdateStatus(assignmentStrategy, distributionSetEntity, targetEntitiesIdsChunks);
final Map<TargetWithActionType, JpaAction> assignedActions =
createActions(targetsWithActionType, targetEntities, distributionSetEntity, assignmentStrategy, initiatedBy);
createActions(targetsWithActionType, targetEntities, distributionSetEntity, assignmentStrategy);
// create initial action status when action is created, so we remember
// the initial running status because we will change the status
// of the action itself and with this action status we have a nicer action history.
@@ -888,7 +852,7 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
private void checkMaxAssignmentQuota(final String controllerId, final long requested) {
final int quota = quotaManagement.getMaxActionsPerTarget();
try {
systemSecurityContext.runAsSystem(() -> QuotaHelper.assertAssignmentQuota(
asSystem(() -> QuotaHelper.assertAssignmentQuota(
controllerId, requested, quota, Action.class, Target.class, actionRepository::countByTargetControllerId));
} catch (final AssignmentQuotaExceededException ex) {
targetRepository.findByControllerId(controllerId).ifPresentOrElse(
@@ -908,18 +872,18 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
}
}
private void setAssignedDistributionSetAndTargetUpdateStatus(final AbstractDsAssignmentStrategy assignmentStrategy,
private void setAssignedDistributionSetAndTargetUpdateStatus(
final AbstractDsAssignmentStrategy assignmentStrategy,
final JpaDistributionSet set, final List<List<Long>> targetIdsChunks) {
final String currentUser = auditorProvider.getCurrentAuditor().orElse(null);
assignmentStrategy.setAssignedDistributionSetAndTargetStatus(set, targetIdsChunks, currentUser);
assignmentStrategy.setAssignedDistributionSetAndTargetStatus(set, targetIdsChunks);
}
private Map<TargetWithActionType, JpaAction> createActions(final Collection<TargetWithActionType> targetsWithActionType,
final List<JpaTarget> targets, final JpaDistributionSet set, final AbstractDsAssignmentStrategy assignmentStrategy,
final String initiatedBy) {
private Map<TargetWithActionType, JpaAction> createActions(
final Collection<TargetWithActionType> targetsWithActionType,
final List<JpaTarget> targets, final JpaDistributionSet set, final AbstractDsAssignmentStrategy assignmentStrategy) {
final Map<TargetWithActionType, JpaAction> persistedActions = new LinkedHashMap<>();
for (final TargetWithActionType twt : targetsWithActionType) {
final JpaAction targetAction = assignmentStrategy.createTargetAction(initiatedBy, twt, targets, set);
final JpaAction targetAction = assignmentStrategy.createTargetAction(twt, targets, set);
if (targetAction != null) {
persistedActions.put(twt, actionRepository.save(targetAction));
}
@@ -1054,15 +1018,11 @@ public class JpaDeploymentManagement extends JpaActionManagement implements Depl
}
private boolean isMultiAssignmentsEnabled() {
return TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).isMultiAssignmentsEnabled();
return TenantConfigHelper.isMultiAssignmentsEnabled();
}
private boolean isConfirmationFlowEnabled() {
return TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).isConfirmationFlowEnabled();
}
private <T extends Serializable> T getConfigValue(final String key, final Class<T> valueType) {
return systemSecurityContext.runAsSystem(() -> tenantConfigurationManagement.getConfigurationValue(key, valueType).getValue());
return TenantConfigHelper.isUserConfirmationFlowEnabled();
}
private void assertTargetReadAllowed(final Long targetId) {

View File

@@ -9,10 +9,13 @@
*/
package org.eclipse.hawkbit.repository.jpa.management;
import static org.eclipse.hawkbit.context.AccessContext.asSystem;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.DistributionSetInvalidationManagement;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
@@ -26,8 +29,6 @@ import org.eclipse.hawkbit.repository.model.ActionCancellationType;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetInvalidation;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.integration.support.locks.LockRegistry;
import org.springframework.stereotype.Service;
@@ -47,9 +48,7 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet
private final TargetFilterQueryManagement<? extends TargetFilterQuery> targetFilterQueryManagement;
private final PlatformTransactionManager txManager;
private final RepositoryProperties repositoryProperties;
private final TenantAware tenantAware;
private final LockRegistry lockRegistry;
private final SystemSecurityContext systemSecurityContext;
@SuppressWarnings("java:S107")
protected JpaDistributionSetInvalidationManagement(
@@ -57,23 +56,20 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet
final RolloutManagement rolloutManagement, final DeploymentManagement deploymentManagement,
final TargetFilterQueryManagement<? extends TargetFilterQuery> targetFilterQueryManagement,
final PlatformTransactionManager txManager, final RepositoryProperties repositoryProperties,
final TenantAware tenantAware, final LockRegistry lockRegistry,
final SystemSecurityContext systemSecurityContext) {
final LockRegistry lockRegistry) {
this.distributionSetManagement = distributionSetManagement;
this.rolloutManagement = rolloutManagement;
this.deploymentManagement = deploymentManagement;
this.targetFilterQueryManagement = targetFilterQueryManagement;
this.txManager = txManager;
this.repositoryProperties = repositoryProperties;
this.tenantAware = tenantAware;
this.lockRegistry = lockRegistry;
this.systemSecurityContext = systemSecurityContext;
}
@Override
public void invalidateDistributionSet(final DistributionSetInvalidation distributionSetInvalidation) {
log.debug("Invalidate distribution sets {}", distributionSetInvalidation.getDistributionSetIds());
final String tenant = tenantAware.getCurrentTenant();
final String tenant = AccessContext.tenant();
if (shouldRolloutsBeCanceled(distributionSetInvalidation.getActionCancellationType())) {
final String handlerId = JpaRolloutManagement.createRolloutLockKey(tenant);
final Lock lock = lockRegistry.obtain(handlerId);
@@ -131,7 +127,7 @@ public class JpaDistributionSetInvalidationManagement implements DistributionSet
}
// Do run as system to ensure all actions (even invisible) are canceled due to invalidation.
systemSecurityContext.runAsSystem(() -> {
asSystem(() -> {
log.debug("Cancel auto assignments after ds invalidation. ID: {}", setId);
targetFilterQueryManagement.cancelAutoAssignmentForDistributionSet(setId);
});

View File

@@ -34,12 +34,12 @@ import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.exception.DeletedException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetTag;
@@ -56,8 +56,6 @@ import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.Statistic;
import org.eclipse.hawkbit.repository.qfields.DistributionSetFields;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.utils.TenantConfigHelper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.data.domain.Page;
@@ -83,7 +81,6 @@ public class JpaDistributionSetManagement
private final DistributionSetTagRepository distributionSetTagRepository;
private final TargetFilterQueryRepository targetFilterQueryRepository;
private final QuotaManagement quotaManagement;
private final TenantConfigHelper tenantConfigHelper;
private final RepositoryProperties repositoryProperties;
@SuppressWarnings("java:S107")
@@ -95,8 +92,6 @@ public class JpaDistributionSetManagement
final DistributionSetTagRepository distributionSetTagRepository,
final TargetFilterQueryRepository targetFilterQueryRepository,
final QuotaManagement quotaManagement,
final SystemSecurityContext systemSecurityContext,
final TenantConfigurationManagement tenantConfigurationManagement,
final RepositoryProperties repositoryProperties) {
super(jpaRepository, entityManager);
this.distributionSetTagManagement = distributionSetTagManagement;
@@ -104,7 +99,6 @@ public class JpaDistributionSetManagement
this.distributionSetTagRepository = distributionSetTagRepository;
this.targetFilterQueryRepository = targetFilterQueryRepository;
this.quotaManagement = quotaManagement;
this.tenantConfigHelper = TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement);
this.repositoryProperties = repositoryProperties;
}
@@ -209,7 +203,7 @@ public class JpaDistributionSetManagement
return false;
}
if (Boolean.FALSE.equals(tenantConfigHelper.getConfigValue(IMPLICIT_LOCK_ENABLED, Boolean.class))) {
if (Boolean.FALSE.equals(TenantConfigHelper.getAsSystem(IMPLICIT_LOCK_ENABLED, Boolean.class))) {
// implicit lock disabled
return false;
}

View File

@@ -11,10 +11,10 @@ package org.eclipse.hawkbit.repository.jpa.management;
import jakarta.persistence.EntityManager;
import org.eclipse.hawkbit.repository.qfields.DistributionSetTagFields;
import org.eclipse.hawkbit.repository.DistributionSetTagManagement;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetTag;
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTagRepository;
import org.eclipse.hawkbit.repository.qfields.DistributionSetTagFields;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.stereotype.Service;

View File

@@ -18,7 +18,6 @@ import java.util.Optional;
import jakarta.persistence.EntityManager;
import org.eclipse.hawkbit.repository.qfields.DistributionSetTypeFields;
import org.eclipse.hawkbit.repository.DistributionSetTypeManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
@@ -36,6 +35,7 @@ import org.eclipse.hawkbit.repository.jpa.specifications.DistributionSetTypeSpec
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.qfields.DistributionSetTypeFields;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.cache.Cache;

View File

@@ -71,18 +71,16 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
private final ActionRepository actionRepository;
private final TargetRepository targetRepository;
private final EntityManager entityManager;
private final RolloutStatusCache rolloutStatusCache;
@SuppressWarnings("java:S107")
protected JpaRolloutGroupManagement(final RolloutGroupRepository rolloutGroupRepository,
final RolloutRepository rolloutRepository, final ActionRepository actionRepository,
final TargetRepository targetRepository, final EntityManager entityManager, final RolloutStatusCache rolloutStatusCache) {
final TargetRepository targetRepository, final EntityManager entityManager) {
this.rolloutGroupRepository = rolloutGroupRepository;
this.rolloutRepository = rolloutRepository;
this.actionRepository = actionRepository;
this.targetRepository = targetRepository;
this.entityManager = entityManager;
this.rolloutStatusCache = rolloutStatusCache;
}
@Override
@@ -118,10 +116,10 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
final JpaRolloutGroup jpaRolloutGroup = (JpaRolloutGroup) rolloutGroupRepository.findById(rolloutGroupId).map(RolloutGroup.class::cast)
.orElseThrow(() -> new EntityNotFoundException(RolloutGroup.class, rolloutGroupId));
List<TotalTargetCountActionStatus> rolloutStatusCountItems = rolloutStatusCache.getRolloutGroupStatus(rolloutGroupId);
List<TotalTargetCountActionStatus> rolloutStatusCountItems = RolloutStatusCache.getRolloutGroupStatus(rolloutGroupId);
if (CollectionUtils.isEmpty(rolloutStatusCountItems)) {
rolloutStatusCountItems = actionRepository.getStatusCountByRolloutGroupId(rolloutGroupId);
rolloutStatusCache.putRolloutGroupStatus(rolloutGroupId, rolloutStatusCountItems);
RolloutStatusCache.putRolloutGroupStatus(rolloutGroupId, rolloutStatusCountItems);
}
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(
@@ -225,7 +223,7 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
}
private Map<Long, List<TotalTargetCountActionStatus>> getStatusCountItemForRolloutGroup(final List<Long> groupIds) {
final Map<Long, List<TotalTargetCountActionStatus>> fromCache = rolloutStatusCache
final Map<Long, List<TotalTargetCountActionStatus>> fromCache = RolloutStatusCache
.getRolloutGroupStatus(groupIds);
final List<Long> rolloutGroupIds = groupIds.stream().filter(id -> !fromCache.containsKey(id)).toList();
@@ -235,7 +233,7 @@ public class JpaRolloutGroupManagement implements RolloutGroupManagement {
final Map<Long, List<TotalTargetCountActionStatus>> fromDb = resultList.stream()
.collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId));
rolloutStatusCache.putRolloutGroupStatus(fromDb);
RolloutStatusCache.putRolloutGroupStatus(fromDb);
fromCache.putAll(fromDb);
}

View File

@@ -29,9 +29,9 @@ import jakarta.validation.ValidationException;
import jakarta.validation.constraints.NotNull;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.ContextAware;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.im.authentication.SpRole;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.auth.SpRole;
import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.ql.jpa.QLSupport;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
@@ -42,7 +42,6 @@ import org.eclipse.hawkbit.repository.RolloutHelper;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.RolloutStatusCache;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.event.EventPublisherHolder;
import org.eclipse.hawkbit.repository.event.remote.entity.RolloutGroupCreatedEvent;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
@@ -51,6 +50,7 @@ import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetExcepti
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
import org.eclipse.hawkbit.repository.exception.RolloutIllegalStateException;
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
import org.eclipse.hawkbit.repository.jpa.Jpa;
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
@@ -85,10 +85,7 @@ import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TotalTargetCountActionStatus;
import org.eclipse.hawkbit.repository.model.TotalTargetCountStatus;
import org.eclipse.hawkbit.repository.qfields.RolloutFields;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.utils.ObjectCopyUtil;
import org.eclipse.hawkbit.utils.TenantConfigHelper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.dao.ConcurrencyFailureException;
@@ -129,17 +126,13 @@ public class JpaRolloutManagement implements RolloutManagement {
private final RolloutGroupRepository rolloutGroupRepository;
private final RolloutApprovalStrategy rolloutApprovalStrategy;
private final StartNextGroupRolloutGroupSuccessAction startNextRolloutGroupAction;
private final RolloutStatusCache rolloutStatusCache;
private final ActionRepository actionRepository;
private final TargetManagement<? extends Target> targetManagement;
private final ActionStatusRepository actionStatusRepository;
private final DistributionSetManagement<? extends DistributionSet> distributionSetManagement;
private final TenantAware tenantAware;
private final TenantConfigurationManagement tenantConfigurationManagement;
private final QuotaManagement quotaManagement;
private final SystemSecurityContext systemSecurityContext;
private final ContextAware contextAware;
private final RepositoryProperties repositoryProperties;
private final OnlineDsAssignmentStrategy onlineDsAssignmentStrategy;
protected JpaRolloutManagement(
@@ -148,35 +141,26 @@ public class JpaRolloutManagement implements RolloutManagement {
final RolloutGroupRepository rolloutGroupRepository,
final RolloutApprovalStrategy rolloutApprovalStrategy,
final StartNextGroupRolloutGroupSuccessAction startNextRolloutGroupAction,
final RolloutStatusCache rolloutStatusCache,
final ActionRepository actionRepository,
final ActionStatusRepository actionStatusRepository,
final TargetRepository targetRepository,
final TargetManagement<? extends Target> targetManagement,
final DistributionSetManagement<? extends DistributionSet> distributionSetManagement,
final TenantAware tenantAware,
final TenantConfigurationManagement tenantConfigurationManagement,
final QuotaManagement quotaManagement,
final SystemSecurityContext systemSecurityContext, final ContextAware contextAware,
final RepositoryProperties repositoryProperties) {
this.entityManager = entityManager;
this.rolloutRepository = rolloutRepository;
this.rolloutGroupRepository = rolloutGroupRepository;
this.rolloutApprovalStrategy = rolloutApprovalStrategy;
this.startNextRolloutGroupAction = startNextRolloutGroupAction;
this.rolloutStatusCache = rolloutStatusCache;
this.actionRepository = actionRepository;
this.actionStatusRepository = actionStatusRepository;
this.targetManagement = targetManagement;
this.distributionSetManagement = distributionSetManagement;
this.tenantAware = tenantAware;
this.tenantConfigurationManagement = tenantConfigurationManagement;
this.quotaManagement = quotaManagement;
this.systemSecurityContext = systemSecurityContext;
this.contextAware = contextAware;
this.repositoryProperties = repositoryProperties;
this.onlineDsAssignmentStrategy = new OnlineDsAssignmentStrategy(targetRepository, actionRepository, actionStatusRepository,
onlineDsAssignmentStrategy = new OnlineDsAssignmentStrategy(targetRepository, actionRepository, actionStatusRepository,
quotaManagement, this::isMultiAssignmentsEnabled, this::isConfirmationFlowEnabled, repositoryProperties, null);
}
@@ -223,20 +207,21 @@ public class JpaRolloutManagement implements RolloutManagement {
} else {
RolloutHelper.verifyRolloutGroupAmount(amountGroup, quotaManagement);
}
final JpaRollout rolloutRequest = new JpaRollout();
ObjectCopyUtil.copy(rollout, rolloutRequest, false, UnaryOperator.identity());
rolloutRequest.setDynamic(rollout.isDynamic()); // TODO - copy compares isDynamic == false and don't set it to false - remain null
// scheduled rollout, the creator shall have permissions to start rollout
if (rolloutRequest.getStartAt() != null && rolloutRequest.getStartAt() != Long.MAX_VALUE && // if scheduled rollout
!systemSecurityContext.hasPermission(SpPermission.HANDLE_ROLLOUT) &&
!systemSecurityContext.hasPermission(SpRole.SYSTEM_ROLE)) {
throw new InsufficientPermissionException("You need permission to start rollouts to create a scheduled rollout");
}
if (dynamicRolloutGroupTemplate != null && !rolloutRequest.isDynamic()) {
if (dynamicRolloutGroupTemplate != null && !rollout.isDynamic()) {
throw new ValidationException("Dynamic group template is only allowed for dynamic rollouts");
}
// scheduled rollout, the creator shall have permissions to start rollout
if (rollout.getStartAt() != null && rollout.getStartAt() != Long.MAX_VALUE && // if scheduled rollout
!SpPermission.hasPermission(SpPermission.HANDLE_ROLLOUT) &&
!SpPermission.hasPermission(SpRole.SYSTEM_ROLE)) {
throw new InsufficientPermissionException("You need permission to start rollouts to create a scheduled rollout");
}
final JpaRollout rolloutRequest = new JpaRollout();
ObjectCopyUtil.copy(rollout, rolloutRequest, false, UnaryOperator.identity());
// TODO - copy compares isDynamic == false and don't set it to false - remain null
rolloutRequest.setDynamic(rollout.isDynamic());
return createRolloutGroups(
amountGroup, conditions, createRollout(rolloutRequest, amountGroup == 0), confirmationRequired, dynamicRolloutGroupTemplate);
}
@@ -318,11 +303,11 @@ public class JpaRolloutManagement implements RolloutManagement {
final Rollout rollout = rolloutRepository.findById(rolloutId).map(Rollout.class::cast)
.orElseThrow(() -> new EntityNotFoundException(Rollout.class, rolloutId));
List<TotalTargetCountActionStatus> rolloutStatusCountItems = rolloutStatusCache.getRolloutStatus(rolloutId);
List<TotalTargetCountActionStatus> rolloutStatusCountItems = RolloutStatusCache.getRolloutStatus(rolloutId);
if (CollectionUtils.isEmpty(rolloutStatusCountItems)) {
rolloutStatusCountItems = actionRepository.getStatusCountByRolloutId(rolloutId);
rolloutStatusCache.putRolloutStatus(rolloutId, rolloutStatusCountItems);
RolloutStatusCache.putRolloutStatus(rolloutId, rolloutStatusCountItems);
}
final TotalTargetCountStatus totalTargetCountStatus = new TotalTargetCountStatus(
@@ -520,8 +505,8 @@ public class JpaRolloutManagement implements RolloutManagement {
private void softCancelActionsOfRollout(final Rollout rollout) {
final List<JpaAction> actions = actionRepository.findAll(
ActionSpecifications
.byRolloutIdAndActiveAndStatusIsNot(rollout.getId(),
List.of(Action.Status.CANCELING)), // avoid cancelling state here, because it is count as still active
// avoid cancelling state here, because it is count as still active
.byRolloutIdAndActiveAndStatusIsNot(rollout.getId(), List.of(Action.Status.CANCELING)),
Pageable.ofSize(maxActions))
.getContent();
log.info("Found {} active actions for rollout {}, performing soft cancel.", actions.size(), rollout.getId());
@@ -529,7 +514,7 @@ public class JpaRolloutManagement implements RolloutManagement {
storeActionsAndStatuses(actions, Action.Status.CANCELING);
// send cancellation messages to event publisher
onlineDsAssignmentStrategy.sendCancellationMessages(actions, tenantAware.getCurrentTenant());
onlineDsAssignmentStrategy.sendCancellationMessages(actions, AccessContext.tenant());
}
private void forceQuitActionsOfRollout(final Rollout rollout) {
@@ -592,7 +577,7 @@ public class JpaRolloutManagement implements RolloutManagement {
Jpa.setNativeQueryInParameter(updateQuery, "tid", targetIds);
final int updated = updateQuery.executeUpdate();
log.info("{} of target assigned distribution values updated for tenant {}",
updated, tenantAware.getCurrentTenant());
updated, AccessContext.tenant());
return updated;
}
@@ -607,7 +592,7 @@ public class JpaRolloutManagement implements RolloutManagement {
Jpa.setNativeQueryInParameter(updateQuery, "tid", targetIds);
final int updated = updateQuery.executeUpdate();
log.info("{} of target assigned distribution set to previously installed distribution value for tenant {}",
updated, tenantAware.getCurrentTenant());
updated, AccessContext.tenant());
return updated;
}
@@ -693,7 +678,7 @@ public class JpaRolloutManagement implements RolloutManagement {
}
private JpaRollout createRollout(final JpaRollout rollout, final boolean pureDynamic) {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(rollout);
WeightValidationHelper.validate(rollout);
rollout.setCreatedAt(System.currentTimeMillis());
@@ -725,7 +710,7 @@ public class JpaRolloutManagement implements RolloutManagement {
if (rollout.getWeight().isEmpty()) {
rollout.setWeight(repositoryProperties.getActionWeightIfAbsent());
}
contextAware.getCurrentContext().ifPresent(rollout::setAccessControlContext);
AccessContext.securityContext().ifPresent(rollout::setAccessControlContext);
return rollout;
}
@@ -747,8 +732,7 @@ public class JpaRolloutManagement implements RolloutManagement {
JpaRolloutGroup lastGroup = null;
if (amountOfGroups == 0) {
if (dynamicRolloutGroupTemplate == null) {
throw new ConstraintDeclarationException(
"At least one static rollout group must be defined for a static rollout");
throw new ConstraintDeclarationException("At least one static rollout group must be defined for a static rollout");
}
} else {
// we can enforce the 'max targets per group' quota right here because
@@ -890,7 +874,7 @@ public class JpaRolloutManagement implements RolloutManagement {
return Collections.emptyMap();
}
final Map<Long, List<TotalTargetCountActionStatus>> fromCache = rolloutStatusCache.getRolloutStatus(rollouts);
final Map<Long, List<TotalTargetCountActionStatus>> fromCache = RolloutStatusCache.getRolloutStatus(rollouts);
final List<Long> rolloutIds = rollouts.stream().filter(id -> !fromCache.containsKey(id)).toList();
if (!rolloutIds.isEmpty()) {
@@ -898,7 +882,7 @@ public class JpaRolloutManagement implements RolloutManagement {
final Map<Long, List<TotalTargetCountActionStatus>> fromDb = resultList.stream()
.collect(Collectors.groupingBy(TotalTargetCountActionStatus::getId));
rolloutStatusCache.putRolloutStatus(fromDb);
RolloutStatusCache.putRolloutStatus(fromDb);
fromCache.putAll(fromDb);
}
@@ -1012,11 +996,11 @@ public class JpaRolloutManagement implements RolloutManagement {
}
private boolean isMultiAssignmentsEnabled() {
return TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).isMultiAssignmentsEnabled();
return TenantConfigHelper.isMultiAssignmentsEnabled();
}
private boolean isConfirmationFlowEnabled() {
return TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).isConfirmationFlowEnabled();
return TenantConfigHelper.isUserConfirmationFlowEnabled();
}
private record TargetCount(long total, String filter) {}

View File

@@ -26,7 +26,6 @@ import jakarta.persistence.EntityManager;
import org.eclipse.hawkbit.artifact.encryption.ArtifactEncryptionService;
import org.eclipse.hawkbit.repository.ArtifactManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.qfields.SoftwareModuleFields;
import org.eclipse.hawkbit.repository.SoftwareModuleManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.exception.IncompleteSoftwareModuleException;
@@ -42,6 +41,7 @@ import org.eclipse.hawkbit.repository.model.Artifact;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModule.MetadataValue;
import org.eclipse.hawkbit.repository.qfields.SoftwareModuleFields;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.dao.ConcurrencyFailureException;
import org.springframework.data.domain.Page;

View File

@@ -14,12 +14,12 @@ import java.util.Optional;
import jakarta.persistence.EntityManager;
import org.eclipse.hawkbit.repository.qfields.SoftwareModuleTypeFields;
import org.eclipse.hawkbit.repository.SoftwareModuleTypeManagement;
import org.eclipse.hawkbit.repository.jpa.model.JpaSoftwareModuleType;
import org.eclipse.hawkbit.repository.jpa.repository.DistributionSetTypeRepository;
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleRepository;
import org.eclipse.hawkbit.repository.jpa.repository.SoftwareModuleTypeRepository;
import org.eclipse.hawkbit.repository.qfields.SoftwareModuleTypeFields;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.cache.Cache;

View File

@@ -9,6 +9,8 @@
*/
package org.eclipse.hawkbit.repository.jpa.management;
import static org.eclipse.hawkbit.context.AccessContext.asSystemAsTenant;
import java.util.Set;
import java.util.function.Consumer;
@@ -16,9 +18,9 @@ import jakarta.persistence.EntityManager;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.artifact.ArtifactStorage;
import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.TenantStatsManagement;
import org.eclipse.hawkbit.repository.event.EventPublisherHolder;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.jpa.CurrentTenantCacheKeyGenerator;
@@ -43,10 +45,6 @@ import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
import org.eclipse.hawkbit.repository.model.TenantMetaData;
import org.eclipse.hawkbit.repository.model.report.SystemUsageReport;
import org.eclipse.hawkbit.repository.model.report.SystemUsageReportWithTenants;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager.CacheEvictEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
@@ -57,7 +55,6 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.lang.Nullable;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Service;
@@ -78,9 +75,6 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
private static final int MAX_TENANTS_QUERY = 1000;
private final String countArtifactQuery;
private final String countSoftwareModulesQuery;
private final TargetRepository targetRepository;
private final TargetTypeRepository targetTypeRepository;
private final TargetTagRepository targetTagRepository;
@@ -93,10 +87,7 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
private final RolloutRepository rolloutRepository;
private final TenantConfigurationRepository tenantConfigurationRepository;
private final TenantMetaDataRepository tenantMetaDataRepository;
private final TenantStatsManagement systemStatsManagement;
private final SystemManagementCacheKeyGenerator currentTenantCacheKeyGenerator;
private final SystemSecurityContext systemSecurityContext;
private final TenantAware tenantAware;
private final PlatformTransactionManager txManager;
private final EntityManager entityManager;
private final RepositoryProperties repositoryProperties;
@@ -112,8 +103,8 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
final DistributionSetRepository distributionSetRepository, final DistributionSetTypeRepository distributionSetTypeRepository,
final DistributionSetTagRepository distributionSetTagRepository, final RolloutRepository rolloutRepository,
final TenantConfigurationRepository tenantConfigurationRepository, final TenantMetaDataRepository tenantMetaDataRepository,
final TenantStatsManagement systemStatsManagement, final SystemManagementCacheKeyGenerator currentTenantCacheKeyGenerator,
final SystemSecurityContext systemSecurityContext, final TenantAware tenantAware, final PlatformTransactionManager txManager,
final SystemManagementCacheKeyGenerator currentTenantCacheKeyGenerator,
final PlatformTransactionManager txManager,
final EntityManager entityManager, final RepositoryProperties repositoryProperties,
final JpaProperties properties) {
this.targetRepository = targetRepository;
@@ -128,17 +119,10 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
this.rolloutRepository = rolloutRepository;
this.tenantConfigurationRepository = tenantConfigurationRepository;
this.tenantMetaDataRepository = tenantMetaDataRepository;
this.systemStatsManagement = systemStatsManagement;
this.currentTenantCacheKeyGenerator = currentTenantCacheKeyGenerator;
this.systemSecurityContext = systemSecurityContext;
this.tenantAware = tenantAware;
this.txManager = txManager;
this.entityManager = entityManager;
this.repositoryProperties = repositoryProperties;
final String isDeleted = isPostgreSql(properties) ? "false" : "0";
countArtifactQuery = "SELECT COUNT(a.id) FROM sp_artifact a INNER JOIN sp_software_module sm ON a.software_module = sm.id WHERE sm.deleted = " + isDeleted;
countSoftwareModulesQuery = "select SUM(file_size) from sp_artifact a INNER JOIN sp_software_module sm ON a.software_module = sm.id WHERE sm.deleted = " + isDeleted;
}
@Autowired(required = false) // it's not required on dmf/ddi only instances
@@ -152,34 +136,6 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
return currentTenantCacheKeyGenerator.currentTenantKeyGenerator();
}
@Override
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void deleteTenant(final String t) {
if (artifactStorage == null) {
throw new IllegalStateException("Artifact repository is not available. Can't delete tenant.");
}
final String tenant = t.toUpperCase();
tenantAware.runAsTenant(tenant, () -> DeploymentHelper.runInNewTransaction(txManager, "deleteTenant", status -> {
tenantMetaDataRepository.deleteByTenantIgnoreCase(tenant);
tenantConfigurationRepository.deleteByTenant(tenant);
targetRepository.deleteByTenant(tenant);
targetFilterQueryRepository.deleteByTenant(tenant);
rolloutRepository.deleteByTenant(tenant);
targetTypeRepository.deleteByTenant(tenant);
targetTagRepository.deleteByTenant(tenant);
distributionSetTagRepository.deleteByTenant(tenant);
distributionSetRepository.deleteByTenant(tenant);
distributionSetTypeRepository.deleteByTenant(tenant);
softwareModuleRepository.deleteByTenant(tenant);
artifactStorage.deleteByTenant(tenant);
softwareModuleTypeRepository.deleteByTenant(tenant);
return null;
}));
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new CacheEvictEvent.Default(tenant, null, null));
}
@Override
public Page<String> findTenants(final Pageable pageable) {
return tenantMetaDataRepository.findTenants(pageable);
@@ -190,51 +146,21 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
// Exception squid:S2229 - calling findTenants without transaction is intended in this case
@SuppressWarnings("squid:S2229")
public void forEachTenant(final Consumer<String> consumer) {
forEachTenant0(consumer);
}
private void forEachTenant0(final Consumer<String> consumer) {
Page<String> tenants;
Pageable query = PageRequest.of(0, MAX_TENANTS_QUERY);
do {
tenants = findTenants(query);
tenants.forEach(tenant -> tenantAware.runAsTenant(tenant, () -> {
tenants = findTenants(query); // with IS_SYSTEM_CODE so we could find all tenants
tenants.forEach(tenant -> asSystemAsTenant(tenant, () -> {
try {
consumer.accept(tenant);
} catch (final RuntimeException ex) {
log.debug("Exception on forEachTenant execution for tenant {}. Continue with next tenant.",
tenant, ex);
log.error("Exception on forEachTenant execution for tenant {} with error message [{}]. "
+ "Continue with next tenant.", tenant, ex.getMessage());
log.error("Exception on forEachTenant execution for tenant {} with error message [{}]. Continue with next tenant.",
tenant, ex.getMessage());
}
return null;
}));
} while ((query = tenants.nextPageable()) != Pageable.unpaged());
}
@Override
public SystemUsageReportWithTenants getSystemUsageStatisticsWithTenants() {
final SystemUsageReportWithTenants result = (SystemUsageReportWithTenants) getSystemUsageStatistics();
usageStatsPerTenant(result);
return result;
}
@Override
public SystemUsageReport getSystemUsageStatistics() {
final Number count = (Number) entityManager.createNativeQuery(countSoftwareModulesQuery).getSingleResult();
long sumOfArtifacts = 0;
if (count != null) {
sumOfArtifacts = count.longValue();
}
// we use native queries to punch through the tenant boundaries. This has to be used with care!
final long targets = ((Number) entityManager.createNativeQuery("SELECT COUNT(id) FROM sp_target").getSingleResult()).longValue();
final long artifacts = ((Number) entityManager.createNativeQuery(countArtifactQuery).getSingleResult()).longValue();
final long actions = ((Number) entityManager.createNativeQuery("SELECT COUNT(id) FROM sp_action").getSingleResult()).longValue();
return new SystemUsageReportWithTenants(targets, artifacts, actions, sumOfArtifacts, tenantMetaDataRepository.count());
}
@Override
public TenantMetaData getTenantMetadata() {
return getTenantMetadata0(true);
@@ -265,23 +191,46 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
return tenantMetaDataRepository.save(data);
}
private static boolean isPostgreSql(final JpaProperties properties) {
return Database.POSTGRESQL == properties.getDatabase();
@Override
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
backoff = @Backoff(delay = Constants.TX_RT_DELAY))
public void deleteTenant(final String t) {
if (artifactStorage == null) {
throw new IllegalStateException("Artifact repository is not available. Can't delete tenant.");
}
final String tenant = t.toUpperCase();
asSystemAsTenant(tenant, () -> DeploymentHelper.runInNewTransaction(txManager, "deleteTenant", status -> {
tenantMetaDataRepository.deleteByTenantIgnoreCase(tenant);
tenantConfigurationRepository.deleteByTenant(tenant);
targetRepository.deleteByTenant(tenant);
targetFilterQueryRepository.deleteByTenant(tenant);
rolloutRepository.deleteByTenant(tenant);
targetTypeRepository.deleteByTenant(tenant);
targetTagRepository.deleteByTenant(tenant);
distributionSetTagRepository.deleteByTenant(tenant);
distributionSetRepository.deleteByTenant(tenant);
distributionSetTypeRepository.deleteByTenant(tenant);
softwareModuleRepository.deleteByTenant(tenant);
artifactStorage.deleteByTenant(tenant);
softwareModuleTypeRepository.deleteByTenant(tenant);
return null;
}));
EventPublisherHolder.getInstance().getEventPublisher().publishEvent(new CacheEvictEvent.Default(tenant, null, null));
}
private TenantMetaData getTenantMetadata0(final boolean withDetails) {
final String tenant = tenantAware.getCurrentTenant();
final String tenant = AccessContext.tenant();
if (tenant == null) {
throw new IllegalStateException("Tenant not set");
throw new IllegalStateException("AccessContext not set");
}
final TenantMetaData metaData =
withDetails ?
tenantMetaDataRepository.findWitDetailsByTenantIgnoreCase(tenant) :
tenantMetaDataRepository.findByTenantIgnoreCase(tenant);
final TenantMetaData metaData = withDetails
? tenantMetaDataRepository.findWitDetailsByTenantIgnoreCase(tenant)
: tenantMetaDataRepository.findByTenantIgnoreCase(tenant);
if (metaData == null) {
if (repositoryProperties.isImplicitTenantCreateAllowed()) {
log.info("Tenant {} doesn't exist create metadata", tenant, new Exception("Thread dump"));
log.info("AccessContext {} doesn't exist create metadata", tenant, new Exception("Thread dump"));
return createTenantMetadata0(tenant);
} else {
throw new EntityNotFoundException(TenantMetaData.class, tenant);
@@ -291,10 +240,6 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
}
}
private void usageStatsPerTenant(final SystemUsageReportWithTenants report) {
forEachTenant0(tenant -> report.addTenantData(systemStatsManagement.getStatsOfTenant()));
}
private DistributionSetType createStandardSoftwareDataSetup() {
final SoftwareModuleType app = softwareModuleTypeRepository.save(
new JpaSoftwareModuleType(
@@ -338,23 +283,20 @@ public class JpaSystemManagement implements CurrentTenantCacheKeyGenerator, Syst
}
/**
* Creating the initial tenant meta-data in a new transaction. Due to the
* {@link org.eclipse.hawkbit.repository.jpa.configuration.MultiTenantJpaTransactionManager} is using the current tenant to
* set the necessary tenant discriminator to the query. This is not working
* if we don't have a current tenant set. Due to the
* {@link #createTenantMetadata(String)} is maybe called without having a
* current tenant we need to re-open a new transaction so the
* {@link org.eclipse.hawkbit.repository.jpa.configuration.MultiTenantJpaTransactionManager} is called again and set the
* Creating the initial tenant meta-data in a new transaction. Due to the tenant support it is using the current tenant to
* set the necessary tenant discriminator to the query. This is not working if we don't have a current tenant set.
* Due to the {@link #createTenantMetadata(String)} is maybe called without having a
* current tenant we need to re-open a new transaction so the tenant support is called again and set the
* tenant for this transaction.
*
* @param tenant the tenant to be created
* @return the initial created {@link TenantMetaData}
*/
private TenantMetaData createInitialTenantMetaData(final String tenant) {
return systemSecurityContext.runAsSystemAsTenant(
() -> DeploymentHelper.runInNewTransaction(txManager, "initial-tenant-creation", status -> {
return asSystemAsTenant(
tenant, () -> DeploymentHelper.runInNewTransaction(txManager, "initial-tenant-creation", status -> {
final DistributionSetType defaultDsType = createStandardSoftwareDataSetup();
return tenantMetaDataRepository.save(new JpaTenantMetaData(defaultDsType, tenant));
}), tenant);
}));
}
}
}

View File

@@ -18,19 +18,19 @@ import jakarta.persistence.EntityManager;
import cz.jirutka.rsql.parser.RSQLParserException;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.ContextAware;
import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.ql.jpa.QLSupport;
import org.eclipse.hawkbit.repository.DistributionSetManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.exception.IncompleteDistributionSetException;
import org.eclipse.hawkbit.repository.exception.InvalidAutoAssignActionTypeException;
import org.eclipse.hawkbit.repository.exception.InvalidDistributionSetException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
import org.eclipse.hawkbit.repository.jpa.JpaManagementHelper;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetFilterQuery;
@@ -44,10 +44,7 @@ import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
import org.eclipse.hawkbit.repository.qfields.TargetFields;
import org.eclipse.hawkbit.repository.qfields.TargetFilterQueryFields;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.utils.TenantConfigHelper;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.data.domain.AuditorAware;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
@@ -66,34 +63,26 @@ import org.springframework.validation.annotation.Validated;
@Service
@ConditionalOnBooleanProperty(prefix = "hawkbit.jpa", name = { "enabled", "target-filter-management" }, matchIfMissing = true)
class JpaTargetFilterQueryManagement
extends AbstractJpaRepositoryManagement<JpaTargetFilterQuery, TargetFilterQueryManagement.Create, TargetFilterQueryManagement.Update, TargetFilterQueryRepository, TargetFilterQueryFields>
extends
AbstractJpaRepositoryManagement<JpaTargetFilterQuery, TargetFilterQueryManagement.Create, TargetFilterQueryManagement.Update, TargetFilterQueryRepository, TargetFilterQueryFields>
implements TargetFilterQueryManagement<JpaTargetFilterQuery> {
private final TargetManagement<? extends Target> targetManagement;
private final DistributionSetManagement<? extends DistributionSet> distributionSetManagement;
private final QuotaManagement quotaManagement;
private final TenantConfigurationManagement tenantConfigurationManagement;
private final RepositoryProperties repositoryProperties;
private final SystemSecurityContext systemSecurityContext;
private final ContextAware contextAware;
private final AuditorAware<String> auditorAware;
protected JpaTargetFilterQueryManagement(
final TargetFilterQueryRepository targetFilterQueryRepository, final EntityManager entityManager,
final TargetManagement<? extends Target> targetManagement,
final DistributionSetManagement<? extends DistributionSet> distributionSetManagement,
final QuotaManagement quotaManagement, final TenantConfigurationManagement tenantConfigurationManagement,
final RepositoryProperties repositoryProperties,
final SystemSecurityContext systemSecurityContext, final ContextAware contextAware, final AuditorAware<String> auditorAware) {
final QuotaManagement quotaManagement,
final RepositoryProperties repositoryProperties) {
super(targetFilterQueryRepository, entityManager);
this.targetManagement = targetManagement;
this.distributionSetManagement = distributionSetManagement;
this.quotaManagement = quotaManagement;
this.tenantConfigurationManagement = tenantConfigurationManagement;
this.repositoryProperties = repositoryProperties;
this.systemSecurityContext = systemSecurityContext;
this.contextAware = contextAware;
this.auditorAware = auditorAware;
}
@Override
@@ -161,7 +150,7 @@ class JpaTargetFilterQueryManagement
targetFilterQuery.setAutoAssignInitiatedBy(null);
targetFilterQuery.setConfirmationRequired(false);
} else {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(update);
WeightValidationHelper.validate(update);
assertMaxTargetsQuota(targetFilterQuery.getQuery(), targetFilterQuery.getName(), update.dsId());
DistributionSet distributionSet = distributionSetManagement.getValidAndComplete(update.dsId());
@@ -170,12 +159,13 @@ class JpaTargetFilterQueryManagement
}
targetFilterQuery.setAutoAssignDistributionSet(distributionSet);
contextAware.getCurrentContext().ifPresent(targetFilterQuery::setAccessControlContext);
targetFilterQuery.setAutoAssignInitiatedBy(auditorAware.getCurrentAuditor().orElse(targetFilterQuery.getCreatedBy()));
AccessContext.securityContext().ifPresent(targetFilterQuery::setAccessControlContext);
targetFilterQuery.setAutoAssignInitiatedBy(Optional.ofNullable(AccessContext.actor()).orElse(targetFilterQuery.getCreatedBy()));
targetFilterQuery.setAutoAssignActionType(sanitizeAutoAssignActionType(update.actionType()));
targetFilterQuery.setAutoAssignWeight(update.weight() == null ? repositoryProperties.getActionWeightIfAbsent() : update.weight());
final boolean confirmationRequired =
update.confirmationRequired() == null ? isConfirmationFlowEnabled() : update.confirmationRequired();
final boolean confirmationRequired = update.confirmationRequired() == null
? TenantConfigHelper.isUserConfirmationFlowEnabled()
: update.confirmationRequired();
targetFilterQuery.setConfirmationRequired(confirmationRequired);
}
return jpaRepository.save(targetFilterQuery);
@@ -200,10 +190,6 @@ class JpaTargetFilterQueryManagement
return actionType;
}
private boolean isConfirmationFlowEnabled() {
return TenantConfigHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).isConfirmationFlowEnabled();
}
private void assertMaxTargetsQuota(final String query, final String filterName, final long dsId) {
QuotaHelper.assertAssignmentQuota(filterName,
targetManagement.countByRsqlAndNonDsAndCompatibleAndUpdatable(dsId, query),
@@ -230,7 +216,7 @@ class JpaTargetFilterQueryManagement
// enforce the 'max targets per auto assign' quota right here even if the result of the filter query can vary over time
Optional.ofNullable(create.getAutoAssignDistributionSet()).ifPresent(dsId -> {
WeightValidationHelper.usingContext(systemSecurityContext, tenantConfigurationManagement).validate(create);
WeightValidationHelper.validate(create);
assertMaxTargetsQuota(query, create.getName(), dsId.getId());
});
});

View File

@@ -11,10 +11,10 @@ package org.eclipse.hawkbit.repository.jpa.management;
import jakarta.persistence.EntityManager;
import org.eclipse.hawkbit.repository.qfields.TargetTagFields;
import org.eclipse.hawkbit.repository.TargetTagManagement;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;
import org.eclipse.hawkbit.repository.jpa.repository.TargetTagRepository;
import org.eclipse.hawkbit.repository.qfields.TargetTagFields;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

View File

@@ -16,7 +16,6 @@ import java.util.function.ToLongFunction;
import jakarta.persistence.EntityManager;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.qfields.TargetTypeFields;
import org.eclipse.hawkbit.repository.TargetTypeManagement;
import org.eclipse.hawkbit.repository.exception.AssignmentQuotaExceededException;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
@@ -31,6 +30,7 @@ import org.eclipse.hawkbit.repository.jpa.specifications.TargetTypeSpecification
import org.eclipse.hawkbit.repository.jpa.utils.QuotaHelper;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.TargetType;
import org.eclipse.hawkbit.repository.qfields.TargetTypeFields;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.cache.Cache;

View File

@@ -9,7 +9,7 @@
*/
package org.eclipse.hawkbit.repository.jpa.management;
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_GATEWAY_SECURITY_TOKEN;
import static org.eclipse.hawkbit.auth.SpPermission.READ_GATEWAY_SECURITY_TOKEN;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.BATCH_ASSIGNMENTS_ENABLED;
import static org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey.MULTI_ASSIGNMENTS_ENABLED;
@@ -30,12 +30,14 @@ import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.ql.jpa.QLSupport;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.exception.InsufficientPermissionException;
import org.eclipse.hawkbit.repository.exception.TenantConfigurationValidatorException;
import org.eclipse.hawkbit.repository.exception.TenantConfigurationValueChangeNotAllowedException;
import org.eclipse.hawkbit.repository.helper.SystemSecurityContextHolder;
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.model.JpaTarget;
import org.eclipse.hawkbit.repository.jpa.model.JpaTenantConfiguration;
@@ -45,7 +47,6 @@ import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.model.TenantConfiguration;
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
import org.eclipse.hawkbit.repository.qfields.TargetFields;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAwareCacheManager;
import org.eclipse.hawkbit.tenancy.configuration.DurationHelper;
import org.eclipse.hawkbit.tenancy.configuration.PollingTime;
@@ -54,6 +55,8 @@ import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.convert.ConversionException;
import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
@@ -73,7 +76,7 @@ import org.springframework.validation.annotation.Validated;
@Validated
@Service
@ConditionalOnBooleanProperty(prefix = "hawkbit.jpa", name = { "enabled", "tenant-configuration-management" }, matchIfMissing = true)
public class JpaTenantConfigurationManagement implements TenantConfigurationManagement {
public class JpaTenantConfigurationManagement implements TenantConfigurationManagement, ApplicationListener<ContextRefreshedEvent> {
private static final String CACHE_TENANT_CONFIGURATION_NAME = JpaTenantConfiguration.class.getSimpleName();
private static final ConfigurableConversionService CONVERSION_SERVICE = new DefaultConversionService();
@@ -91,6 +94,12 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
this.applicationContext = applicationContext;
}
@Override
public void onApplicationEvent(final ContextRefreshedEvent event) {
// Sets the proxy / bean from the context in order to be used via proxy and onore things like @PreAuthorize and @Transactional
TenantConfigHelper.setTenantConfigurationManagement(applicationContext.getBean(JpaTenantConfigurationManagement.class));
}
@Override
@Transactional
@Retryable(retryFor = { ConcurrencyFailureException.class }, maxAttempts = Constants.TX_RT_MAX,
@@ -170,8 +179,7 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
private void checkAccess(final String keyName) {
if (AUTHENTICATION_GATEWAY_SECURITY_TOKEN_KEY.equalsIgnoreCase(keyName)) {
final SystemSecurityContext systemSecurityContext = SystemSecurityContextHolder.getInstance().getSystemSecurityContext();
if (!SystemSecurityContext.isCurrentThreadSystemCode() && !systemSecurityContext.hasPermission(READ_GATEWAY_SECURITY_TOKEN)) {
if (!AccessContext.isCurrentThreadSystemCode() && !SpPermission.hasPermission(READ_GATEWAY_SECURITY_TOKEN)) {
throw new InsufficientPermissionException(
"Can't read gateway security token! " + READ_GATEWAY_SECURITY_TOKEN + " is required!");
}
@@ -214,7 +222,8 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
.build()));
}
private <T extends Serializable> void validateConfigurationValue(final T value, final TenantConfigurationKey configurationKey, final Object convertedValue) {
private <T extends Serializable> void validateConfigurationValue(final T value, final TenantConfigurationKey configurationKey,
final Object convertedValue) {
configurationKey.validate(convertedValue, applicationContext);
// additional validation for specific configuration keys
if (POLLING_TIME.equals(configurationKey.getKeyName())) {
@@ -254,7 +263,6 @@ public class JpaTenantConfigurationManagement implements TenantConfigurationMana
return convertedValue;
}
@SuppressWarnings("unchecked")
private <T extends Serializable> TenantConfigurationValue<T> getConfigurationValue0(final String keyName, final Class<T> propertyType) {
checkAccess(keyName);

View File

@@ -9,12 +9,12 @@
*/
package org.eclipse.hawkbit.repository.jpa.management;
import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.repository.TenantStatsManagement;
import org.eclipse.hawkbit.repository.jpa.repository.ActionRepository;
import org.eclipse.hawkbit.repository.jpa.repository.ArtifactRepository;
import org.eclipse.hawkbit.repository.jpa.repository.TargetRepository;
import org.eclipse.hawkbit.repository.model.report.TenantUsage;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
@@ -32,21 +32,18 @@ public class JpaTenantStatsManagement implements TenantStatsManagement {
private final TargetRepository targetRepository;
private final ArtifactRepository artifactRepository;
private final ActionRepository actionRepository;
private final TenantAware tenantAware;
protected JpaTenantStatsManagement(
final TargetRepository targetRepository, final ArtifactRepository artifactRepository, final ActionRepository actionRepository,
final TenantAware tenantAware) {
final TargetRepository targetRepository, final ArtifactRepository artifactRepository, final ActionRepository actionRepository) {
this.targetRepository = targetRepository;
this.artifactRepository = artifactRepository;
this.actionRepository = actionRepository;
this.tenantAware = tenantAware;
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public TenantUsage getStatsOfTenant() {
final String tenant = tenantAware.getCurrentTenant();
final String tenant = AccessContext.tenant();
final TenantUsage result = new TenantUsage(tenant);

View File

@@ -15,6 +15,7 @@ import java.util.function.Consumer;
import java.util.function.Function;
import org.apache.commons.collections4.ListUtils;
import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryConstants;
import org.eclipse.hawkbit.repository.RepositoryProperties;
@@ -22,7 +23,6 @@ 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;
@@ -54,9 +54,9 @@ class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
}
@Override
public JpaAction createTargetAction(final String initiatedBy, final TargetWithActionType targetWithActionType,
final List<JpaTarget> targets, final JpaDistributionSet set) {
final JpaAction result = super.createTargetAction(initiatedBy, targetWithActionType, targets, set);
public JpaAction createTargetAction(
final TargetWithActionType targetWithActionType, final List<JpaTarget> targets, final JpaDistributionSet set) {
final JpaAction result = super.createTargetAction(targetWithActionType, targets, set);
if (result != null) {
result.setStatus(Status.FINISHED);
result.setActive(Boolean.FALSE);
@@ -94,8 +94,7 @@ class OfflineDsAssignmentStrategy 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 long now = System.currentTimeMillis();
targetIds.forEach(targetIdsChunk -> {
if (targetRepository.count(AccessController.Operation.UPDATE,
@@ -103,7 +102,7 @@ class OfflineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
throw new InsufficientPermissionException("No update access to all targets!");
}
targetRepository.setAssignedAndInstalledDistributionSetAndUpdateStatus(
TargetUpdateStatus.IN_SYNC, set, now, currentUser, targetIdsChunk);
TargetUpdateStatus.IN_SYNC, set, now, AccessContext.actor(), targetIdsChunk);
// TODO AC - current problem with this approach is that the caller detach the targets and seems doesn't save them
// targetRepository.saveAll(
// targetRepository

View File

@@ -19,6 +19,7 @@ import java.util.function.Function;
import java.util.stream.Stream;
import org.apache.commons.collections4.ListUtils;
import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.repository.QuotaManagement;
import org.eclipse.hawkbit.repository.RepositoryProperties;
import org.eclipse.hawkbit.repository.event.EventPublisherHolder;
@@ -74,9 +75,9 @@ class OnlineDsAssignmentStrategy extends AbstractDsAssignmentStrategy {
}
@Override
public JpaAction createTargetAction(final String initiatedBy, final TargetWithActionType targetWithActionType,
public JpaAction createTargetAction(final TargetWithActionType targetWithActionType,
final List<JpaTarget> targets, final JpaDistributionSet set) {
final JpaAction result = super.createTargetAction(initiatedBy, targetWithActionType, targets, set);
final JpaAction result = super.createTargetAction(targetWithActionType, targets, set);
if (result != null) {
final boolean confirmationRequired = targetWithActionType.isConfirmationRequired()
&& result.getTarget().getAutoConfirmationStatus() == null;
@@ -125,15 +126,15 @@ 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 long now = System.currentTimeMillis();
targetIds.forEach(targetIdsChunk -> {
if (targetRepository.count(AccessController.Operation.UPDATE,
targetRepository.byIdsSpec(targetIdsChunk)) != targetIdsChunk.size()) {
throw new InsufficientPermissionException("No update access to all targets!");
}
targetRepository.setAssignedDistributionSetAndUpdateStatus(set, now, currentUser, TargetUpdateStatus.PENDING, targetIdsChunk);
targetRepository.setAssignedDistributionSetAndUpdateStatus(
set, now, AccessContext.actor(), TargetUpdateStatus.PENDING, targetIdsChunk);
// TODO AC - current problem with this approach is that the caller detach the targets and seems doesn't save them
// targetRepository.saveAll(
// targetRepository

View File

@@ -11,7 +11,6 @@ package org.eclipse.hawkbit.repository.jpa.model;
import java.io.Serial;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@@ -33,7 +32,6 @@ import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetTypeCre
import org.eclipse.hawkbit.repository.event.remote.entity.DistributionSetTypeUpdatedEvent;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.eclipse.hawkbit.repository.model.SoftwareModule;
import org.eclipse.hawkbit.repository.model.SoftwareModuleType;
/**

View File

@@ -48,11 +48,13 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.im.authentication.SpPermission;
import org.eclipse.hawkbit.auth.SpPermission;
import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.repository.event.EventPublisherHolder;
import org.eclipse.hawkbit.repository.event.remote.TargetDeletedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetCreatedEvent;
import org.eclipse.hawkbit.repository.event.remote.entity.TargetUpdatedEvent;
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
import org.eclipse.hawkbit.repository.jpa.utils.MapAttributeConverter;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.PollStatus;
@@ -60,9 +62,6 @@ import org.eclipse.hawkbit.repository.model.Target;
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.repository.helper.SystemSecurityContextHolder;
import org.eclipse.hawkbit.repository.helper.TenantConfigurationManagementHolder;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.springframework.util.ObjectUtils;
/**
@@ -215,8 +214,7 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
@Override
public String getSecurityToken() {
final SystemSecurityContext systemSecurityContext = SystemSecurityContextHolder.getInstance().getSystemSecurityContext();
if (SystemSecurityContext.isCurrentThreadSystemCode() || systemSecurityContext.hasPermission(SpPermission.READ_TARGET_SECURITY_TOKEN)) {
if (AccessContext.isCurrentThreadSystemCode() || SpPermission.hasPermission(SpPermission.READ_TARGET_SECURITY_TOKEN)) {
return securityToken;
}
return null;
@@ -232,7 +230,7 @@ public class JpaTarget extends AbstractJpaNamedEntity implements Target, EventAw
if (lastTargetQuery == null) {
return null;
}
return TenantConfigurationManagementHolder.getInstance().getTenantConfigurationManagement()
return TenantConfigHelper.getTenantConfigurationManagement()
.pollStatusResolver()
.apply(this);
}

View File

@@ -35,7 +35,7 @@ import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.eclipse.hawkbit.repository.model.TenantMetaData;
/**
* Tenant entity with meta data that is configured globally for the entire
* AccessContext entity with meta data that is configured globally for the entire
* tenant. This entity is not tenant aware to allow the system to access it
* through the {@link EntityManager} even before the actual tenant exists.
*

View File

@@ -9,7 +9,6 @@
*/
package org.eclipse.hawkbit.repository.jpa.repository;
import java.util.Collection;
import java.util.List;
import java.util.Optional;

View File

@@ -10,12 +10,10 @@
package org.eclipse.hawkbit.repository.jpa.repository;
import java.util.List;
import java.util.Optional;
import jakarta.persistence.EntityManager;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetTag;
import org.eclipse.hawkbit.repository.model.DistributionSetTag;
import org.eclipse.hawkbit.repository.model.TargetTag;
import org.eclipse.hawkbit.repository.model.TenantAwareBaseEntity;
import org.springframework.data.jpa.repository.Modifying;

View File

@@ -9,8 +9,6 @@
*/
package org.eclipse.hawkbit.repository.jpa.repository;
import java.util.Optional;
import jakarta.persistence.EntityManager;
import org.eclipse.hawkbit.repository.jpa.model.JpaTargetTag;

View File

@@ -9,14 +9,14 @@
*/
package org.eclipse.hawkbit.repository.jpa.rollout.condition;
import static org.eclipse.hawkbit.context.AccessContext.asSystem;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.exception.EntityNotFoundException;
import org.eclipse.hawkbit.repository.jpa.model.JpaRolloutGroup;
import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
import org.eclipse.hawkbit.security.SystemSecurityContext;
/**
* Error action evaluator which pauses the whole {@link Rollout} and sets the
@@ -25,16 +25,12 @@ import org.eclipse.hawkbit.security.SystemSecurityContext;
public class PauseRolloutGroupAction implements RolloutGroupActionEvaluator<RolloutGroup.RolloutGroupErrorAction> {
private final RolloutManagement rolloutManagement;
private final RolloutGroupRepository rolloutGroupRepository;
private final SystemSecurityContext systemSecurityContext;
public PauseRolloutGroupAction(final RolloutManagement rolloutManagement,
final RolloutGroupRepository rolloutGroupRepository, final SystemSecurityContext systemSecurityContext) {
final RolloutGroupRepository rolloutGroupRepository) {
this.rolloutManagement = rolloutManagement;
this.rolloutGroupRepository = rolloutGroupRepository;
this.systemSecurityContext = systemSecurityContext;
}
@Override
@@ -44,23 +40,19 @@ public class PauseRolloutGroupAction implements RolloutGroupActionEvaluator<Roll
@Override
public void exec(final Rollout rollout, final RolloutGroup rolloutG) {
final JpaRolloutGroup rolloutGroup = (JpaRolloutGroup) rolloutG;
systemSecurityContext.runAsSystem(() -> {
rolloutGroup.setStatus(RolloutGroupStatus.ERROR);
rolloutGroupRepository.save(rolloutGroup);
/*
Refresh latest rollout state in order to escape cases when
previous group have matched error condition and paused the rollout
and this one tries to pause the rollout too but throws an exception
and rollbacks rollout processing transaction
*/
final Rollout refreshedRollout = rolloutManagement.get(rollout.getId());
if (Rollout.RolloutStatus.PAUSED != refreshedRollout.getStatus()) {
// if only the latest state is != paused then pause
rolloutManagement.pauseRollout(rollout.getId());
}
});
rolloutGroup.setStatus(RolloutGroupStatus.ERROR);
rolloutGroupRepository.save(rolloutGroup);
// Refresh latest rollout state in order to avoid cases when
// previous group have matched error condition and paused the rollout
// and this one tries to pause the rollout too but throws an exception
// and rollbacks rollout processing transaction
final Rollout refreshedRollout = rolloutManagement.get(rollout.getId());
if (Rollout.RolloutStatus.PAUSED != refreshedRollout.getStatus()) {
// if only the latest state is != paused then pause
// execute as system if rollout creator has CREATE_ROLLOUT right but no HANDLE_ROLLOUT
asSystem(() -> rolloutManagement.pauseRollout(rollout.getId()));
}
}
}

View File

@@ -9,13 +9,14 @@
*/
package org.eclipse.hawkbit.repository.jpa.rollout.condition;
import static org.eclipse.hawkbit.context.AccessContext.asSystem;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.jpa.repository.RolloutGroupRepository;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.repository.model.RolloutGroup;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
import org.eclipse.hawkbit.security.SystemSecurityContext;
/**
* Success action which starts the next following {@link RolloutGroup}.
@@ -25,14 +26,11 @@ public class StartNextGroupRolloutGroupSuccessAction implements RolloutGroupActi
private final RolloutGroupRepository rolloutGroupRepository;
private final DeploymentManagement deploymentManagement;
private final SystemSecurityContext systemSecurityContext;
public StartNextGroupRolloutGroupSuccessAction(
final RolloutGroupRepository rolloutGroupRepository, final DeploymentManagement deploymentManagement,
final SystemSecurityContext systemSecurityContext) {
final RolloutGroupRepository rolloutGroupRepository, final DeploymentManagement deploymentManagement) {
this.rolloutGroupRepository = rolloutGroupRepository;
this.deploymentManagement = deploymentManagement;
this.systemSecurityContext = systemSecurityContext;
}
@Override
@@ -40,11 +38,12 @@ public class StartNextGroupRolloutGroupSuccessAction implements RolloutGroupActi
return RolloutGroup.RolloutGroupSuccessAction.NEXTGROUP;
}
// Note - the exec could be called by JpaRolloutsExecutor and buy JpaRolloutsManagement#triggerNextGroup
// Note - the exec could be called by JpaRolloutsExecutor and by JpaRolloutsManagement#triggerNextGroup
// this means it could be called by concurrently.
@Override
public void exec(final Rollout rollout, final RolloutGroup rolloutGroup) {
systemSecurityContext.runAsSystem(() -> {
// as system so to assume needed permissions. When called the permission to start next group are assumed anyway
asSystem(() -> {
// retrieve all actions according to the parent group of the finished rolloutGroup,
// so retrieve all child-group actions which need to be started.
deploymentManagement.startScheduledActionsByRolloutGroupParent(

View File

@@ -9,6 +9,8 @@
*/
package org.eclipse.hawkbit.repository.jpa.scheduler;
import static org.eclipse.hawkbit.context.AccessContext.asSystem;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
@@ -17,7 +19,6 @@ import io.micrometer.core.instrument.MeterRegistry;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.AutoAssignExecutor;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.DefaultTenantConfiguration;
import org.springframework.integration.support.locks.LockRegistry;
import org.springframework.scheduling.annotation.Scheduled;
@@ -31,24 +32,14 @@ public class AutoAssignScheduler {
private static final String PROP_SCHEDULER_DELAY_PLACEHOLDER = "${hawkbit.scheduler.scheduler.fixedDelay:2000}";
private final SystemManagement systemManagement;
private final SystemSecurityContext systemSecurityContext;
private final AutoAssignExecutor autoAssignExecutor;
private final LockRegistry lockRegistry;
private final Optional<MeterRegistry> meterRegistry;
/**
* Instantiates a new AutoAssignScheduler
*
* @param systemManagement to find all tenants
* @param systemSecurityContext to run as system
* @param autoAssignExecutor to run a check as tenant
* @param lockRegistry to acquire a lock per tenant
*/
public AutoAssignScheduler(final SystemManagement systemManagement,
final SystemSecurityContext systemSecurityContext, final AutoAssignExecutor autoAssignExecutor,
public AutoAssignScheduler(
final SystemManagement systemManagement, final AutoAssignExecutor autoAssignExecutor,
final LockRegistry lockRegistry, final Optional<MeterRegistry> meterRegistry) {
this.systemManagement = systemManagement;
this.systemSecurityContext = systemSecurityContext;
this.autoAssignExecutor = autoAssignExecutor;
this.lockRegistry = lockRegistry;
this.meterRegistry = meterRegistry;
@@ -56,13 +47,12 @@ public class AutoAssignScheduler {
/**
* Scheduler method called by the spring-async mechanism. Retrieves all tenants and runs for each
* tenant the auto assignments defined in the target filter queries {@link SystemSecurityContext}.
* tenant the auto assignments defined in the target filter queries {@link System}.
*/
@Scheduled(initialDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER)
public void autoAssignScheduler() {
// run this code in system code privileged to have the necessary
// permission to query and create entities.
systemSecurityContext.runAsSystem(this::executeAutoAssign);
// run this code in system code privileged to have the necessary permission to query and create entities.
asSystem(this::executeAutoAssign);
}
@SuppressWarnings("squid:S3516")
@@ -75,11 +65,11 @@ public class AutoAssignScheduler {
return null;
}
final long startNano = System.nanoTime();
final long startNano = java.lang.System.nanoTime();
try {
log.debug("Auto assign scheduled execution has acquired lock and started for each tenant.");
systemManagement.forEachTenant(tenant -> {
final long startNanoT = System.nanoTime();
final long startNanoT = java.lang.System.nanoTime();
autoAssignExecutor.checkAllTargets();
@@ -87,13 +77,13 @@ public class AutoAssignScheduler {
.map(mReg -> mReg.timer(
"hawkbit.scheduler.executor",
DefaultTenantConfiguration.TENANT_TAG, tenant))
.ifPresent(timer -> timer.record(System.nanoTime() - startNanoT, TimeUnit.NANOSECONDS));
.ifPresent(timer -> timer.record(java.lang.System.nanoTime() - startNanoT, TimeUnit.NANOSECONDS));
});
} finally {
lock.unlock();
meterRegistry
.map(mReg -> mReg.timer("hawkbit.scheduler.executor.all"))
.ifPresent(timer -> timer.record(System.nanoTime() - startNano, TimeUnit.NANOSECONDS));
.ifPresent(timer -> timer.record(java.lang.System.nanoTime() - startNano, TimeUnit.NANOSECONDS));
log.debug("Auto assign scheduled execution has released lock and finished.");
}

View File

@@ -9,6 +9,9 @@
*/
package org.eclipse.hawkbit.repository.jpa.scheduler;
import static org.eclipse.hawkbit.context.AccessContext.asActor;
import static org.eclipse.hawkbit.context.AccessContext.withSecurityContext;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
@@ -16,12 +19,11 @@ import java.util.function.Consumer;
import jakarta.persistence.PersistenceException;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.ContextAware;
import org.eclipse.hawkbit.exception.AbstractServerRtException;
import org.eclipse.hawkbit.repository.AutoAssignExecutor;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.TargetManagement;
import org.eclipse.hawkbit.repository.AutoAssignExecutor;
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
import org.eclipse.hawkbit.repository.model.Action;
@@ -62,17 +64,15 @@ public class JpaAutoAssignExecutor implements AutoAssignExecutor {
private final TargetManagement<? extends Target> targetManagement;
private final DeploymentManagement deploymentManagement;
private final PlatformTransactionManager transactionManager;
private final ContextAware contextAware;
public JpaAutoAssignExecutor(
final TargetFilterQueryManagement<? extends TargetFilterQuery> targetFilterQueryManagement,
final TargetManagement<? extends Target> targetManagement, final DeploymentManagement deploymentManagement,
final PlatformTransactionManager transactionManager, final ContextAware contextAware) {
final PlatformTransactionManager transactionManager) {
this.targetFilterQueryManagement = targetFilterQueryManagement;
this.targetManagement = targetManagement;
this.deploymentManagement = deploymentManagement;
this.transactionManager = transactionManager;
this.contextAware = contextAware;
}
@Override
@@ -84,7 +84,7 @@ public class JpaAutoAssignExecutor implements AutoAssignExecutor {
}
@Override
public void checkSingleTarget(String controllerId) {
public void checkSingleTarget(final String controllerId) {
log.debug("Auto assign check call for device {} started", controllerId);
forEachFilterWithAutoAssignDS(filter -> checkForDevice(controllerId, filter));
log.debug("Auto assign check call for device {} finished", controllerId);
@@ -127,7 +127,7 @@ public class JpaAutoAssignExecutor implements AutoAssignExecutor {
// run in the context the auto assignment is made in, i.e. if there is access control context it runs in it
// otherwise in the tenant & user context built by createdBy
// Note: It must be called in a tenant context, i.e. contextAware.getCurrentTenant() returns the tenant
// Note: It must be called in a tenant context, i.e. Security.getCurrentTenant() returns the tenant
private void forEachFilterWithAutoAssignDS(final Consumer<TargetFilterQuery> consumer) {
Slice<TargetFilterQuery> filterQueries;
Pageable query = PageRequest.of(0, PAGE_SIZE);
@@ -137,14 +137,10 @@ public class JpaAutoAssignExecutor implements AutoAssignExecutor {
filterQueries.forEach(filterQuery -> {
try {
filterQuery.getAccessControlContext().ifPresentOrElse(
context -> // has stored context - executes it with it
contextAware.runInContext(
context,
() -> consumer.accept(filterQuery)),
() -> // has no stored context - executes it in the tenant & user scope
contextAware.runAsTenantAsUser(
contextAware.getCurrentTenant(),
getAutoAssignmentInitiatedBy(filterQuery), () -> consumer.accept(filterQuery))
// has stored context - executes it with it
context -> withSecurityContext(context, () -> consumer.accept(filterQuery)),
// has no stored context - executes it in the tenant & user scope
() -> asActor(getAutoAssignmentInitiatedBy(filterQuery), () -> consumer.accept(filterQuery))
);
} catch (final RuntimeException ex) {
if (log.isDebugEnabled()) {
@@ -173,7 +169,9 @@ public class JpaAutoAssignExecutor implements AutoAssignExecutor {
final List<DeploymentRequest> deploymentRequests = mapToDeploymentRequests(controllerIds, targetFilterQuery);
final int count = deploymentRequests.size();
if (count > 0) {
deploymentManagement.assignDistributionSets(getAutoAssignmentInitiatedBy(targetFilterQuery), deploymentRequests, actionMessage);
asActor(
getAutoAssignmentInitiatedBy(targetFilterQuery),
() -> deploymentManagement.assignDistributionSets(deploymentRequests, actionMessage));
}
return count;
});

View File

@@ -9,6 +9,9 @@
*/
package org.eclipse.hawkbit.repository.jpa.scheduler;
import static org.eclipse.hawkbit.context.AccessContext.asActor;
import static org.eclipse.hawkbit.context.AccessContext.asSystem;
import static org.eclipse.hawkbit.context.AccessContext.withSecurityContext;
import static org.eclipse.hawkbit.repository.jpa.JpaManagementHelper.combineWithAnd;
import static org.eclipse.hawkbit.repository.jpa.executor.AfterTransactionCommitExecutor.afterCommit;
@@ -25,7 +28,7 @@ import java.util.stream.StreamSupport;
import jakarta.persistence.EntityManager;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.ContextAware;
import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.ql.jpa.QLSupport;
import org.eclipse.hawkbit.repository.DeploymentManagement;
import org.eclipse.hawkbit.repository.QuotaManagement;
@@ -73,9 +76,6 @@ import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupStatus;
import org.eclipse.hawkbit.repository.model.RolloutGroup.RolloutGroupSuccessCondition;
import org.eclipse.hawkbit.repository.model.Target;
import org.eclipse.hawkbit.repository.qfields.TargetFields;
import org.eclipse.hawkbit.security.SpringSecurityAuditorAware;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
@@ -127,9 +127,6 @@ public class JpaRolloutExecutor implements RolloutExecutor {
private final RolloutApprovalStrategy rolloutApprovalStrategy;
private final EntityManager entityManager;
private final PlatformTransactionManager txManager;
private final TenantAware tenantAware;
private final ContextAware contextAware;
private final SystemSecurityContext systemSecurityContext;
private final RepositoryProperties repositoryProperties;
private final Map<Long, AtomicLong> lastDynamicGroupFill = new ConcurrentHashMap<>();
@@ -142,7 +139,6 @@ public class JpaRolloutExecutor implements RolloutExecutor {
final RolloutManagement rolloutManagement, final QuotaManagement quotaManagement,
final RolloutGroupEvaluationManager evaluationManager, final RolloutApprovalStrategy rolloutApprovalStrategy,
final EntityManager entityManager, final PlatformTransactionManager txManager,
final TenantAware tenantAware, final ContextAware contextAware, final SystemSecurityContext systemSecurityContext,
final RepositoryProperties repositoryProperties) {
this.actionRepository = actionRepository;
this.rolloutGroupRepository = rolloutGroupRepository;
@@ -157,19 +153,16 @@ public class JpaRolloutExecutor implements RolloutExecutor {
this.rolloutApprovalStrategy = rolloutApprovalStrategy;
this.entityManager = entityManager;
this.txManager = txManager;
this.tenantAware = tenantAware;
this.contextAware = contextAware;
this.systemSecurityContext = systemSecurityContext;
this.repositoryProperties = repositoryProperties;
}
@Override
public void execute(final Rollout rollout) {
rollout.getAccessControlContext().ifPresentOrElse(
context -> // has stored context - executes it with it
contextAware.runInContext(context, () -> execute0(rollout)),
() -> // has no stored context - executes it in the tenant & user scope
contextAware.runAsTenantAsUser(contextAware.getCurrentTenant(), rollout.getCreatedBy(), () -> execute0(rollout)));
// has stored context - executes it with it
context -> withSecurityContext(context, () -> execute0(rollout)),
// has no stored context - executes it in the tenant & user scope
() -> asActor(rollout.getCreatedBy(), () -> execute0(rollout)));
}
private void execute0(final Rollout rollout) {
@@ -184,36 +177,18 @@ public class JpaRolloutExecutor implements RolloutExecutor {
break;
case STARTING:
// the lastModifiedBy user is probably the user that has actually called the rollout start (unless overridden) - not the creator
SpringSecurityAuditorAware.setAuditorOverride(rollout.getLastModifiedBy());
try {
handleStartingRollout((JpaRollout) rollout);
} finally {
// clear, ALWAYS, the set auditor override
SpringSecurityAuditorAware.clearAuditorOverride();
}
asActor(rollout.getLastModifiedBy(), () -> handleStartingRollout((JpaRollout) rollout));
break;
case RUNNING:
handleRunningRollout((JpaRollout) rollout);
break;
case STOPPING:
// the lastModifiedBy user is probably the user that has actually called the rollout stop (unless overridden) - not the creator
SpringSecurityAuditorAware.setAuditorOverride(rollout.getLastModifiedBy());
try {
handleStopRollout((JpaRollout) rollout);
} finally {
// clear, ALWAYS, the set auditor override
SpringSecurityAuditorAware.clearAuditorOverride();
}
asActor(rollout.getLastModifiedBy(), () -> handleStopRollout((JpaRollout) rollout));
break;
case DELETING:
// the lastModifiedBy user is probably the user that has actually called the rollout delete (unless overridden) - not the creator
SpringSecurityAuditorAware.setAuditorOverride(rollout.getLastModifiedBy());
try {
handleDeleteRollout((JpaRollout) rollout);
} finally {
// clear, ALWAYS, the set auditor override
SpringSecurityAuditorAware.clearAuditorOverride();
}
asActor(rollout.getLastModifiedBy(), () -> handleDeleteRollout((JpaRollout) rollout));
break;
default:
log.error("Rollout in status {} not supposed to be handled!", rollout.getStatus());
@@ -343,12 +318,12 @@ public class JpaRolloutExecutor implements RolloutExecutor {
final List<Long> groupIds = rollout.getRolloutGroups().stream().map(RolloutGroup::getId).toList();
afterCommit(() -> EventPublisherHolder.getInstance().getEventPublisher().publishEvent(
new RolloutStoppedEvent(tenantAware.getCurrentTenant(), rollout.getId(), groupIds)));
new RolloutStoppedEvent(AccessContext.tenant(), rollout.getId(), groupIds)));
}
}
private void handleReadyRollout(final Rollout rollout) {
if (rollout.getStartAt() != null && rollout.getStartAt() <= System.currentTimeMillis()) {
if (rollout.getStartAt() != null && rollout.getStartAt() <= java.lang.System.currentTimeMillis()) {
log.debug("handleReadyRollout called for rollout {} with autostart beyond define time. Switch to STARTING", rollout.getId());
rolloutManagement.start(rollout.getId());
}
@@ -671,7 +646,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
// return if group change is made
private boolean fillDynamicRolloutGroupsWithTargets(final JpaRollout rollout) {
final AtomicLong lastFill = lastDynamicGroupFill.computeIfAbsent(rollout.getId(), id -> new AtomicLong(0));
final long now = System.currentTimeMillis();
final long now = java.lang.System.currentTimeMillis();
if (now - lastFill.get() < repositoryProperties.getDynamicRolloutsMinInvolvePeriodMS()) {
// too early to make another dynamic involvement attempt
return false;
@@ -873,7 +848,7 @@ public class JpaRolloutExecutor implements RolloutExecutor {
try {
QuotaHelper.assertAssignmentQuota(target.getId(), requested, quota, Action.class, Target.class, actionRepository::countByTargetId);
} catch (final AssignmentQuotaExceededException ex) {
systemSecurityContext.runAsSystem(() -> deploymentManagement.handleMaxAssignmentsExceeded(target.getId(), requested, ex));
asSystem(() -> deploymentManagement.handleMaxAssignmentsExceeded(target.getId(), requested, ex));
}
}

View File

@@ -16,12 +16,12 @@ import java.util.concurrent.locks.Lock;
import io.micrometer.core.instrument.MeterRegistry;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.repository.RolloutExecutor;
import org.eclipse.hawkbit.repository.RolloutHandler;
import org.eclipse.hawkbit.repository.RolloutManagement;
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
import org.eclipse.hawkbit.tenancy.DefaultTenantConfiguration;
import org.eclipse.hawkbit.tenancy.TenantAware;
import org.springframework.integration.support.locks.LockRegistry;
import org.springframework.transaction.PlatformTransactionManager;
@@ -31,7 +31,6 @@ import org.springframework.transaction.PlatformTransactionManager;
@Slf4j
public class JpaRolloutHandler implements RolloutHandler {
private final TenantAware tenantAware;
private final RolloutManagement rolloutManagement;
private final RolloutExecutor rolloutExecutor;
private final LockRegistry lockRegistry;
@@ -41,16 +40,14 @@ public class JpaRolloutHandler implements RolloutHandler {
/**
* Constructor
*
* @param tenantAware the {@link TenantAware} bean holding the tenant information
* @param rolloutManagement to fetch rollout related information from the datasource
* @param rolloutExecutor to trigger executions for a specific rollout
* @param lockRegistry to lock processes
* @param txManager transaction manager interface
*/
public JpaRolloutHandler(final TenantAware tenantAware, final RolloutManagement rolloutManagement,
public JpaRolloutHandler(final RolloutManagement rolloutManagement,
final RolloutExecutor rolloutExecutor, final LockRegistry lockRegistry,
final PlatformTransactionManager txManager, final Optional<MeterRegistry> meterRegistry) {
this.tenantAware = tenantAware;
this.rolloutManagement = rolloutManagement;
this.rolloutExecutor = rolloutExecutor;
this.lockRegistry = lockRegistry;
@@ -65,7 +62,7 @@ public class JpaRolloutHandler implements RolloutHandler {
return;
}
final String handlerId = createRolloutLockKey(tenantAware.getCurrentTenant());
final String handlerId = createRolloutLockKey(AccessContext.tenant());
final Lock lock = lockRegistry.obtain(handlerId);
if (!lock.tryLock()) {
if (log.isTraceEnabled()) {
@@ -86,7 +83,7 @@ public class JpaRolloutHandler implements RolloutHandler {
}
});
meterRegistry
.map(mReg -> mReg.timer("hawkbit.rollout.handler.all", DefaultTenantConfiguration.TENANT_TAG, tenantAware.getCurrentTenant()))
.map(mReg -> mReg.timer("hawkbit.rollout.handler.all", DefaultTenantConfiguration.TENANT_TAG, AccessContext.tenant()))
.ifPresent(timer -> timer.record(System.nanoTime() - startNano, TimeUnit.NANOSECONDS));
log.debug("Finished handling of the rollouts.");
@@ -102,7 +99,7 @@ public class JpaRolloutHandler implements RolloutHandler {
return tenant + "-rollout";
}
// run in a tenant context, i.e. contextAware.getCurrentTenant() returns the tenant the rollout is made for
// run in a tenant context, i.e. Security.getCurrentTenant() returns the tenant the rollout is made for
private void handleRolloutInNewTransaction(final long rolloutId, final String handlerId) {
final long startNano = System.nanoTime();
@@ -116,7 +113,7 @@ public class JpaRolloutHandler implements RolloutHandler {
meterRegistry
.map(mReg -> mReg.timer(
"hawkbit.rollout.handler",
DefaultTenantConfiguration.TENANT_TAG, tenantAware.getCurrentTenant(),
DefaultTenantConfiguration.TENANT_TAG, AccessContext.tenant(),
"rollout", String.valueOf(rolloutId)))
.ifPresent(timer -> timer.record(System.nanoTime() - startNano, TimeUnit.NANOSECONDS));
}

View File

@@ -9,6 +9,9 @@
*/
package org.eclipse.hawkbit.repository.jpa.scheduler;
import static org.eclipse.hawkbit.context.AccessContext.asSystem;
import static org.eclipse.hawkbit.context.AccessContext.asSystemAsTenant;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@@ -17,7 +20,6 @@ import lombok.extern.slf4j.Slf4j;
import org.eclipse.hawkbit.repository.RolloutHandler;
import org.eclipse.hawkbit.repository.SystemManagement;
import org.eclipse.hawkbit.repository.jpa.rollout.BlockWhenFullPolicy;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.DefaultTenantConfiguration;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@@ -34,75 +36,63 @@ public class RolloutScheduler {
private final SystemManagement systemManagement;
private final RolloutHandler rolloutHandler;
private final SystemSecurityContext systemSecurityContext;
private final Optional<MeterRegistry> meterRegistry;
private final ThreadPoolTaskExecutor rolloutTaskExecutor;
public RolloutScheduler(
final RolloutHandler rolloutHandler, final SystemManagement systemManagement, final SystemSecurityContext systemSecurityContext,
final RolloutHandler rolloutHandler, final SystemManagement systemManagement,
final int threadPoolSize, final Optional<MeterRegistry> meterRegistry) {
this.systemManagement = systemManagement;
this.rolloutHandler = rolloutHandler;
this.systemSecurityContext = systemSecurityContext;
this.meterRegistry = meterRegistry;
rolloutTaskExecutor = threadPoolTaskExecutor(threadPoolSize);
}
/**
* Scheduler method called by the spring-async mechanism. Retrieves all tenants from the {@link SystemManagement#findTenants} and
* runs for each tenant the {@link RolloutHandler#handleAll()} in the {@link SystemSecurityContext}.
* Scheduler method called by the spring-async mechanism. For all tenants, using {@link SystemManagement#forEachTenant},
* runs the {@link RolloutHandler#handleAll()} scoped to permission of access control context or unscoped (with {@link System}) if null
*/
@Scheduled(initialDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER, fixedDelayString = PROP_SCHEDULER_DELAY_PLACEHOLDER)
public void runningRolloutScheduler() {
log.debug("rollout schedule checker has been triggered.");
final long startNano = System.nanoTime();
final long startNano = java.lang.System.nanoTime();
// run this code in system code privileged to have the necessary
// permission to query and create entities.
systemSecurityContext.runAsSystem(() -> {
// workaround eclipselink that is currently not possible to
// execute a query without multi-tenancy if MultiTenant
// annotation is used.
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=355458. So
// iterate through all tenants and execute the rollout check for
// each tenant separately.
systemManagement.forEachTenant(tenant -> {
if (rolloutTaskExecutor == null) {
handleAll(tenant);
} else {
handleAllAsync(tenant);
}
});
});
// run this code in system code privileged to have the necessary system code permission execute forEachTenant
asSystem(() ->
// workaround eclipselink that is currently not possible to execute a query without multi-tenancy if MultiTenant
// annotation is used. https://bugs.eclipse.org/bugs/show_bug.cgi?id=355458. So
// iterate through all tenants and execute the rollout check for each tenant separately.
systemManagement.forEachTenant(tenant -> {
if (rolloutTaskExecutor == null) {
handleAll(tenant);
} else {
handleAllAsync(tenant);
}
}));
meterRegistry
.map(mReg -> mReg.timer("hawkbit.rollout.scheduler.all"))
.ifPresent(timer -> timer.record(System.nanoTime() - startNano, TimeUnit.NANOSECONDS));
.ifPresent(timer -> timer.record(java.lang.System.nanoTime() - startNano, TimeUnit.NANOSECONDS));
}
private void handleAll(final String tenant) {
log.trace("Handling rollout for tenant: {}", tenant);
final long startNano = System.nanoTime();
final long startNano = java.lang.System.nanoTime();
try {
rolloutHandler.handleAll();
} catch (Exception e) {
} catch (final Exception e) {
log.error("Error processing rollout for tenant {}", tenant, e);
}
meterRegistry
.map(mReg -> mReg.timer(
"hawkbit.rollout.scheduler",
DefaultTenantConfiguration.TENANT_TAG, tenant))
.ifPresent(timer -> timer.record(System.nanoTime() - startNano, TimeUnit.NANOSECONDS));
.map(mReg -> mReg.timer("hawkbit.rollout.scheduler", DefaultTenantConfiguration.TENANT_TAG, tenant))
.ifPresent(timer -> timer.record(java.lang.System.nanoTime() - startNano, TimeUnit.NANOSECONDS));
}
private void handleAllAsync(final String tenant) {
rolloutTaskExecutor.submit(() -> systemSecurityContext.runAsSystemAsTenant(() -> {
handleAll(tenant);
return null;
}, tenant));
rolloutTaskExecutor.submit(() -> asSystemAsTenant(tenant, () -> handleAll(tenant)));
}
private ThreadPoolTaskExecutor threadPoolTaskExecutor(final int threadPoolSize) {

View File

@@ -9,6 +9,8 @@
*/
package org.eclipse.hawkbit.repository.jpa.specifications;
import java.util.List;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.ListJoin;
import jakarta.persistence.criteria.SetJoin;
@@ -29,8 +31,6 @@ import org.eclipse.hawkbit.repository.jpa.model.JpaTarget_;
import org.eclipse.hawkbit.repository.model.Action;
import org.springframework.data.jpa.domain.Specification;
import java.util.List;
/**
* Utility class for {@link Action}s {@link Specification}s. The class provides Spring Data JPQL Specifications.
*/

View File

@@ -11,10 +11,8 @@ package org.eclipse.hawkbit.repository.jpa.specifications;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaNamedEntity_;
import org.eclipse.hawkbit.repository.jpa.model.AbstractJpaTypeEntity_;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType;
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSetType_;
import org.eclipse.hawkbit.repository.model.DistributionSet;
import org.eclipse.hawkbit.repository.model.DistributionSetType;
import org.springframework.data.jpa.domain.Specification;

View File

@@ -11,46 +11,25 @@ package org.eclipse.hawkbit.repository.jpa.utils;
import java.util.List;
import lombok.NoArgsConstructor;
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.exception.NoWeightProvidedInMultiAssignmentModeException;
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
import org.eclipse.hawkbit.repository.model.DeploymentRequest;
import org.eclipse.hawkbit.repository.model.Rollout;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.utils.TenantConfigHelper;
/**
* Utility class to handle weight validation in Rollout, Auto Assignments, and
* Online Assignment.
* Utility class to handle weight validation in Rollout, Auto Assignments, and Online Assignment.
*/
@NoArgsConstructor(access = lombok.AccessLevel.PRIVATE)
public final class WeightValidationHelper {
private final TenantConfigurationManagement tenantConfigurationManagement;
private final SystemSecurityContext systemSecurityContext;
private WeightValidationHelper(
final SystemSecurityContext systemSecurityContext, final TenantConfigurationManagement tenantConfigurationManagement) {
this.systemSecurityContext = systemSecurityContext;
this.tenantConfigurationManagement = tenantConfigurationManagement;
}
/**
* Setting the context of the tenant
*
* @param systemSecurityContext security context used to get the tenant and for execution
* @param tenantConfigurationManagement to get the value from
*/
public static WeightValidationHelper usingContext(
final SystemSecurityContext systemSecurityContext, final TenantConfigurationManagement tenantConfigurationManagement) {
return new WeightValidationHelper(systemSecurityContext, tenantConfigurationManagement);
}
/**
* Validating weights associated with all the {@link DeploymentRequest}s
*
* @param deploymentRequests the {@linkplain List} of {@link DeploymentRequest}s
*/
public void validate(final List<DeploymentRequest> deploymentRequests) {
public static void validate(final List<DeploymentRequest> deploymentRequests) {
final long assignmentsWithWeight = deploymentRequests.stream()
.filter(request -> request.getTargetWithActionType().getWeight() != null).count();
final boolean containsAssignmentWithWeight = assignmentsWithWeight > 0;
@@ -64,7 +43,7 @@ public final class WeightValidationHelper {
*
* @param rollout the {@linkplain Rollout}
*/
public void validate(final Rollout rollout) {
public static void validate(final Rollout rollout) {
validateWeight(rollout.getWeight().orElse(null));
}
@@ -73,7 +52,7 @@ public final class WeightValidationHelper {
*
* @param targetFilterQueryCreate the target filter query
*/
public void validate(final TargetFilterQueryManagement.Create targetFilterQueryCreate) {
public static void validate(final TargetFilterQueryManagement.Create targetFilterQueryCreate) {
validateWeight(targetFilterQueryCreate.getAutoAssignWeight());
}
@@ -82,7 +61,7 @@ public final class WeightValidationHelper {
*
* @param autoAssignDistributionSetUpdate the auto assignment distribution set update
*/
public void validate(final TargetFilterQueryManagement.AutoAssignDistributionSetUpdate autoAssignDistributionSetUpdate) {
public static void validate(final TargetFilterQueryManagement.AutoAssignDistributionSetUpdate autoAssignDistributionSetUpdate) {
validateWeight(autoAssignDistributionSetUpdate.weight());
}
@@ -91,7 +70,7 @@ public final class WeightValidationHelper {
*
* @param weight weight tied to the rollout, auto assignment, or online assignment.
*/
public void validateWeight(final Integer weight) {
public static void validateWeight(final Integer weight) {
final boolean hasWeight = weight != null;
validateWeight(hasWeight, !hasWeight);
}
@@ -102,15 +81,11 @@ public final class WeightValidationHelper {
* @param hasWeight indicator of the weight if it has numerical value
* @param hasNoWeight indicator of the weight if it doesn't have a numerical value
*/
public void validateWeight(final boolean hasWeight, final boolean hasNoWeight) {
public static void validateWeight(final boolean hasWeight, final boolean hasNoWeight) {
// remove bypassing the weight enforcement as soon as weight can be set via UI
final boolean bypassWeightEnforcement = true;
final boolean multiAssignmentsEnabled = TenantConfigHelper
.usingContext(systemSecurityContext, tenantConfigurationManagement)
.isMultiAssignmentsEnabled();
if (bypassWeightEnforcement) {
return;
} else if (multiAssignmentsEnabled && hasNoWeight) {
final boolean multiAssignmentsEnabled = TenantConfigHelper.isMultiAssignmentsEnabled();
if (!bypassWeightEnforcement && multiAssignmentsEnabled && hasNoWeight) {
throw new NoWeightProvidedInMultiAssignmentModeException();
}
}

View File

@@ -12,6 +12,7 @@ package org.eclipse.hawkbit.ql.rsql;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.repository.TargetManagement.Create;
import org.eclipse.hawkbit.repository.exception.RSQLParameterSyntaxException;
import org.eclipse.hawkbit.repository.exception.RSQLParameterUnsupportedFieldException;
@@ -117,7 +118,7 @@ class RsqlActionFieldsTest extends AbstractJpaIntegrationTest {
newAction.setStatus(active ? Status.RUNNING : Status.FINISHED);
newAction.setTarget(target);
newAction.setWeight(45);
newAction.setInitiatedBy(tenantAware.getCurrentUsername());
newAction.setInitiatedBy(AccessContext.actor());
if (extRef != null) {
newAction.setExternalRef(extRef);
}

View File

@@ -12,23 +12,16 @@ package org.eclipse.hawkbit.ql.rsql;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import java.util.concurrent.Callable;
import org.eclipse.hawkbit.repository.TenantConfigurationManagement;
import org.eclipse.hawkbit.repository.helper.SystemSecurityContextHolder;
import org.eclipse.hawkbit.repository.helper.TenantConfigurationManagementHolder;
import org.eclipse.hawkbit.repository.helper.TenantConfigHelper;
import org.eclipse.hawkbit.repository.model.TenantConfigurationValue;
import org.eclipse.hawkbit.repository.rsql.VirtualPropertyResolver;
import org.eclipse.hawkbit.security.SystemSecurityContext;
import org.eclipse.hawkbit.tenancy.configuration.TenantConfigurationProperties.TenantConfigurationKey;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.Mockito;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@@ -45,18 +38,17 @@ class VirtualPropertyResolverTest {
TenantConfigurationValue.<String> builder().value("00:07:37").build();
@MockitoBean
private TenantConfigurationManagement confMgmt;
@MockitoBean
private SystemSecurityContext securityContext;
private TenantConfigurationManagement tenantConfigurationManagement;
private final VirtualPropertyResolver substitutor = new VirtualPropertyResolver();
@BeforeEach
void before() {
when(confMgmt.getConfigurationValue(TenantConfigurationKey.POLLING_TIME, String.class))
when(tenantConfigurationManagement.getConfigurationValue(TenantConfigurationKey.POLLING_TIME, String.class))
.thenReturn(TEST_POLLING_TIME_INTERVAL);
when(confMgmt.getConfigurationValue(TenantConfigurationKey.POLLING_OVERDUE_TIME, String.class))
when(tenantConfigurationManagement.getConfigurationValue(TenantConfigurationKey.POLLING_OVERDUE_TIME, String.class))
.thenReturn(TEST_POLLING_OVERDUE_TIME_INTERVAL);
TenantConfigHelper.setTenantConfigurationManagement(tenantConfigurationManagement);
}
/**
@@ -90,24 +82,9 @@ class VirtualPropertyResolverTest {
@ParameterizedTest
@ValueSource(strings = { "${NOW_TS}", "${OVERDUE_TS}", "${overdue_ts}" })
void resolveNowTimestampPlaceholder(final String placeholder) {
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);
assertThat(resolvedPlaceholders).as("'%s' placeholder was not replaced", placeholder).doesNotContain(placeholder);
}
@Configuration
static class Config {
@Bean
TenantConfigurationManagementHolder tenantConfigurationManagementHolder() {
return TenantConfigurationManagementHolder.getInstance();
}
@Bean
SystemSecurityContextHolder systemSecurityContextHolder() {
return SystemSecurityContextHolder.getInstance();
}
}
}

View File

@@ -8,7 +8,9 @@
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.repository.event.remote;
import java.util.Map;
import org.eclipse.hawkbit.event.EventJacksonMessageConverter;
import org.eclipse.hawkbit.event.EventProtoStuffMessageConverter;
import org.eclipse.hawkbit.repository.event.TenantAwareEvent;

View File

@@ -13,6 +13,7 @@ import static org.assertj.core.api.Assertions.assertThat;
import java.util.List;
import org.eclipse.hawkbit.context.AccessContext;
import org.eclipse.hawkbit.repository.jpa.model.JpaAction;
import org.eclipse.hawkbit.repository.model.Action;
import org.eclipse.hawkbit.repository.model.Action.ActionType;
@@ -25,7 +26,7 @@ import org.junit.jupiter.api.Test;
* Feature: Component Tests - Repository<br/>
* Story: RemoteTenantAwareEvent Tests
*/
class RemoteTenantAwareEventTest extends AbstractRemoteEventTest {
class RemoteTenantEventTest extends AbstractRemoteEventTest {
private static final String TENANT_DEFAULT = "DEFAULT";
@@ -93,7 +94,7 @@ class RemoteTenantAwareEventTest extends AbstractRemoteEventTest {
generateAction.setTarget(testdataFactory.createTarget("Test"));
generateAction.setDistributionSet(dsA);
generateAction.setStatus(Status.RUNNING);
generateAction.setInitiatedBy(tenantAware.getCurrentUsername());
generateAction.setInitiatedBy(AccessContext.actor());
generateAction.setWeight(1000);
final Action action = actionRepository.save(generateAction);
@@ -120,7 +121,7 @@ class RemoteTenantAwareEventTest extends AbstractRemoteEventTest {
generateAction.setTarget(testdataFactory.createTarget("Test"));
generateAction.setDistributionSet(dsA);
generateAction.setStatus(Status.RUNNING);
generateAction.setInitiatedBy(tenantAware.getCurrentUsername());
generateAction.setInitiatedBy(AccessContext.actor());
generateAction.setWeight(1000);
final Action action = actionRepository.save(generateAction);

Some files were not shown because too many files have changed in this diff Show More