Fix EntityMatcher when for Identifiable.getId (#2724)
* Fix EntityMatcher to process properly filters of type targetType.id - to resolve correctly the getter return type Long not T * Add AutoAsssignTest access control test * Simplify rest of the ACM tests Signed-off-by: Avgustin Marinov <Avgustin.Marinov@bosch.com>
This commit is contained in:
2
.github/workflows/first-interaction.yaml
vendored
2
.github/workflows/first-interaction.yaml
vendored
@@ -17,6 +17,8 @@ jobs:
|
|||||||
- uses: actions/first-interaction@v3
|
- uses: actions/first-interaction@v3
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.PAT_SECRET }}
|
repo_token: ${{ secrets.PAT_SECRET }}
|
||||||
|
issue_message: |-
|
||||||
|
Thanks @${{ github.actor }} for submitting an issue to hawkBit!! Make yourself comfortable while I'm looking for a committer to assist you.
|
||||||
pr_message: |-
|
pr_message: |-
|
||||||
Thanks @${{ github.actor }} for taking the time to contribute to hawkBit! We really appreciate this. Make yourself comfortable while I'm looking for a committer to help you with your contribution.
|
Thanks @${{ github.actor }} for taking the time to contribute to hawkBit! We really appreciate this. Make yourself comfortable while I'm looking for a committer to help you with your contribution.
|
||||||
Please make sure you read the [contribution guide](https://github.com/eclipse-hawkbit/hawkbit/blob/master/CONTRIBUTING.md) and signed the Eclipse Contributor Agreement (ECA).
|
Please make sure you read the [contribution guide](https://github.com/eclipse-hawkbit/hawkbit/blob/master/CONTRIBUTING.md) and signed the Eclipse Contributor Agreement (ECA).
|
||||||
@@ -61,12 +61,5 @@ jobs:
|
|||||||
- name: Check file license headers
|
- name: Check file license headers
|
||||||
run: mvn license:check -PcheckLicense --batch-mode
|
run: mvn license:check -PcheckLicense --batch-mode
|
||||||
|
|
||||||
- name: Check code style
|
|
||||||
run: |
|
|
||||||
# compare style to target branch
|
|
||||||
git remote add target ${{ github.event.pull_request.base.repo.clone_url }}
|
|
||||||
git fetch target
|
|
||||||
mvn spotless:check -DratchetFrom=${{ github.event.pull_request.base.sha }}
|
|
||||||
|
|
||||||
- name: Run tests & javadoc
|
- name: Run tests & javadoc
|
||||||
run: mvn clean verify javadoc:javadoc -DdetectOfflineLinks=false -PgenerateTestReport ${{ inputs.maven_properties }} --batch-mode
|
run: mvn clean verify javadoc:javadoc -DdetectOfflineLinks=false -PgenerateTestReport ${{ inputs.maven_properties }} --batch-mode
|
||||||
36
.github/workflows/style_check.yaml
vendored
Normal file
36
.github/workflows/style_check.yaml
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
name: Style Check
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- '.3rd-party/**'
|
||||||
|
- 'site/**'
|
||||||
|
- '**.md'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
style_check:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
with:
|
||||||
|
repository: ${{ inputs.repository }}
|
||||||
|
ref: ${{ inputs.ref }}
|
||||||
|
|
||||||
|
- name: Set up JDK
|
||||||
|
uses: actions/setup-java@v5
|
||||||
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: 21
|
||||||
|
cache: 'maven'
|
||||||
|
|
||||||
|
- name: Check code style
|
||||||
|
run: |
|
||||||
|
# compare style to target branch
|
||||||
|
git remote add target ${{ github.event.pull_request.base.repo.clone_url }}
|
||||||
|
git fetch target
|
||||||
|
mvn spotless:check -DratchetFrom=${{ github.event.pull_request.base.sha }}
|
||||||
@@ -31,6 +31,7 @@ import java.util.Objects;
|
|||||||
import java.util.function.BiPredicate;
|
import java.util.function.BiPredicate;
|
||||||
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.ql.Node.Comparison.Operator;
|
import org.eclipse.hawkbit.repository.jpa.ql.Node.Comparison.Operator;
|
||||||
|
import org.springframework.core.ResolvableType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides entity matcher that matches an entity object against a filter (a {@link Node} or an RSQL string).
|
* Provides entity matcher that matches an entity object against a filter (a {@link Node} or an RSQL string).
|
||||||
@@ -57,7 +58,7 @@ public class EntityMatcher {
|
|||||||
return match(t, root);
|
return match(t, root);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings({"java:S3776", "java:S3358", "java:S1125", "java:S6541"}) // better readable this way
|
@SuppressWarnings({ "java:S3776", "java:S3358", "java:S1125", "java:S6541" }) // better readable this way
|
||||||
private <T> boolean match(final T t, final Node node) {
|
private <T> boolean match(final T t, final Node node) {
|
||||||
if (node instanceof Node.Comparison comparison) {
|
if (node instanceof Node.Comparison comparison) {
|
||||||
final String[] split = comparison.getKey().split("\\.", 2);
|
final String[] split = comparison.getKey().split("\\.", 2);
|
||||||
@@ -89,7 +90,8 @@ public class EntityMatcher {
|
|||||||
value = map(comparison.getValue(), getReturnType(valueGetter));
|
value = map(comparison.getValue(), getReturnType(valueGetter));
|
||||||
compare = (e, operator) -> {
|
compare = (e, operator) -> {
|
||||||
try {
|
try {
|
||||||
return compareIgnoreCaseAware(map(e == null ? null : valueGetter.get(e), getReturnType(valueGetter)), operator, value);
|
return compareIgnoreCaseAware(
|
||||||
|
map(e == null ? null : valueGetter.get(e), getReturnType(valueGetter)), operator, value);
|
||||||
} catch (final IllegalAccessException | InvocationTargetException ex) {
|
} catch (final IllegalAccessException | InvocationTargetException ex) {
|
||||||
throw new IllegalArgumentException(ex);
|
throw new IllegalArgumentException(ex);
|
||||||
}
|
}
|
||||||
@@ -143,6 +145,7 @@ public class EntityMatcher {
|
|||||||
private boolean compareIgnoreCaseAware(final Object entityValue, final Operator op, final Object comparisonValue) {
|
private boolean compareIgnoreCaseAware(final Object entityValue, final Operator op, final Object comparisonValue) {
|
||||||
return compare(ignoreCase(entityValue), op, ignoreCase(comparisonValue));
|
return compare(ignoreCase(entityValue), op, ignoreCase(comparisonValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Object ignoreCase(final Object o) {
|
private Object ignoreCase(final Object o) {
|
||||||
if (!ignoreCase || o == null) {
|
if (!ignoreCase || o == null) {
|
||||||
return o;
|
return o;
|
||||||
@@ -157,7 +160,9 @@ public class EntityMatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("java:S3011") // java:S3011 uses reflection to private members anyway
|
// java:S3011 uses reflection to private members anyway
|
||||||
|
// java:S3358 - better readable this way
|
||||||
|
@SuppressWarnings({ "java:S3011", "java:S3358" })
|
||||||
private static <T> Getter getGetter(final Class<T> t, final String fieldName) throws NoSuchMethodException {
|
private static <T> Getter getGetter(final Class<T> t, final String fieldName) throws NoSuchMethodException {
|
||||||
final String[] parts = fieldName.split("\\.");
|
final String[] parts = fieldName.split("\\.");
|
||||||
if (parts.length > 1) {
|
if (parts.length > 1) {
|
||||||
@@ -197,7 +202,12 @@ public class EntityMatcher {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Type type() {
|
public Type type() {
|
||||||
return getter.getGenericReturnType();
|
final Type type = getter.getGenericReturnType();
|
||||||
|
return type instanceof Class<?>
|
||||||
|
? type
|
||||||
|
: type instanceof ParameterizedType
|
||||||
|
? type // Map or Collection generic type
|
||||||
|
: ResolvableType.forMethodReturnType(getter, t).resolve();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
} catch (final NoSuchMethodException e) {
|
} catch (final NoSuchMethodException e) {
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
/**
|
||||||
|
* 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.jpa.acm;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.eclipse.hawkbit.im.authentication.SpPermission;
|
||||||
|
import org.eclipse.hawkbit.repository.DistributionSetFields;
|
||||||
|
import org.eclipse.hawkbit.repository.DistributionSetTypeFields;
|
||||||
|
import org.eclipse.hawkbit.repository.SoftwareModuleFields;
|
||||||
|
import org.eclipse.hawkbit.repository.SoftwareModuleTypeFields;
|
||||||
|
import org.eclipse.hawkbit.repository.TargetFields;
|
||||||
|
import org.eclipse.hawkbit.repository.TargetTagFields;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.model.JpaDistributionSet;
|
||||||
|
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.ql.QLSupport;
|
||||||
|
|
||||||
|
// utility class to validate authorities when ACM is enabled
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public final class AuthorityChecker {
|
||||||
|
|
||||||
|
private static final Set<String> ALL_AUTHORITIES = SpPermission.getAllTenantAuthorities();
|
||||||
|
|
||||||
|
public static String[] validateAuthorities(final String... authorities) {
|
||||||
|
for (final String authority : authorities) {
|
||||||
|
validateAuthority(authority);
|
||||||
|
}
|
||||||
|
return authorities;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void validateAuthority(final String authority) {
|
||||||
|
final int index = authority.indexOf('/');
|
||||||
|
final String unscopedPermission = index > 0 ? authority.substring(0, index) : authority;
|
||||||
|
if (index > 0) {
|
||||||
|
validateScope(group(unscopedPermission), authority.substring(index + 1), authority);
|
||||||
|
}
|
||||||
|
if (!ALL_AUTHORITIES.contains(unscopedPermission)) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Unknown permission: " + unscopedPermission + (index > 0 ? " (unscoped of " + authority + ")" : ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String group(final String unscopedPermission) {
|
||||||
|
if (unscopedPermission.startsWith(SpPermission.CREATE_PREFIX)) {
|
||||||
|
return unscopedPermission.substring(SpPermission.CREATE_PREFIX.length());
|
||||||
|
} else if (unscopedPermission.startsWith(SpPermission.READ_PREFIX)) {
|
||||||
|
return unscopedPermission.substring(SpPermission.READ_PREFIX.length());
|
||||||
|
} else if (unscopedPermission.startsWith(SpPermission.UPDATE_PREFIX)) {
|
||||||
|
return unscopedPermission.substring(SpPermission.UPDATE_PREFIX.length());
|
||||||
|
} else if (unscopedPermission.startsWith(SpPermission.DELETE_PREFIX)) {
|
||||||
|
return unscopedPermission.substring(SpPermission.DELETE_PREFIX.length());
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(unscopedPermission + " doesn't support targetTypeScope");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||||
|
private static void validateScope(final String permission, final String rsql, final String authority) {
|
||||||
|
// validate RSQL
|
||||||
|
final Class<?> rsqlQueryFieldType;
|
||||||
|
final Class<?> jpaType;
|
||||||
|
switch (permission) {
|
||||||
|
case SpPermission.TARGET -> {
|
||||||
|
rsqlQueryFieldType = TargetFields.class;
|
||||||
|
jpaType = JpaTarget.class;
|
||||||
|
}
|
||||||
|
case SpPermission.TARGET_TYPE -> {
|
||||||
|
rsqlQueryFieldType = TargetTagFields.class;
|
||||||
|
jpaType = JpaTarget.class;
|
||||||
|
}
|
||||||
|
case SpPermission.SOFTWARE_MODULE -> {
|
||||||
|
rsqlQueryFieldType = SoftwareModuleFields.class;
|
||||||
|
jpaType = JpaSoftwareModule.class;
|
||||||
|
}
|
||||||
|
case SpPermission.SOFTWARE_MODULE_TYPE -> {
|
||||||
|
rsqlQueryFieldType = SoftwareModuleTypeFields.class;
|
||||||
|
jpaType = JpaSoftwareModuleType.class;
|
||||||
|
}
|
||||||
|
case SpPermission.DISTRIBUTION_SET -> {
|
||||||
|
rsqlQueryFieldType = DistributionSetFields.class;
|
||||||
|
jpaType = JpaDistributionSet.class;
|
||||||
|
}
|
||||||
|
case SpPermission.DISTRIBUTION_SET_TYPE -> {
|
||||||
|
rsqlQueryFieldType = DistributionSetTypeFields.class;
|
||||||
|
jpaType = JpaTarget.class;
|
||||||
|
}
|
||||||
|
default -> throw new IllegalArgumentException(permission + " doesn't support targetTypeScope");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
QLSupport.getInstance().validate(rsql, (Class) rsqlQueryFieldType, jpaType);
|
||||||
|
} catch (final RuntimeException e) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Scope of " + authority + " is not a valid RSQL for " + permission + ": " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (c) 2021 Bosch.IO 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.autoassign;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.eclipse.hawkbit.ContextAware;
|
|
||||||
import org.eclipse.hawkbit.repository.DeploymentManagement;
|
|
||||||
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
|
||||||
import org.eclipse.hawkbit.repository.autoassign.AutoAssignExecutor;
|
|
||||||
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
|
|
||||||
import org.eclipse.hawkbit.repository.model.Action;
|
|
||||||
import org.eclipse.hawkbit.repository.model.DeploymentRequest;
|
|
||||||
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
|
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAware;
|
|
||||||
import org.springframework.data.domain.PageRequest;
|
|
||||||
import org.springframework.data.domain.Pageable;
|
|
||||||
import org.springframework.data.domain.Slice;
|
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
|
||||||
import org.springframework.transaction.annotation.Isolation;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstract implementation of an AutoAssignExecutor
|
|
||||||
*/
|
|
||||||
@Slf4j
|
|
||||||
public abstract class AbstractAutoAssignExecutor implements AutoAssignExecutor {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The message which is added to the action status when a distribution set is
|
|
||||||
* assigned to an target. First %s is the name of the target filter.
|
|
||||||
*/
|
|
||||||
private static final String ACTION_MESSAGE = "Auto assignment by target filter: %s";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maximum for target filter queries with auto assign DS activated.
|
|
||||||
*/
|
|
||||||
private static final int PAGE_SIZE = 1000;
|
|
||||||
|
|
||||||
private final TargetFilterQueryManagement<? extends TargetFilterQuery> targetFilterQueryManagement;
|
|
||||||
private final DeploymentManagement deploymentManagement;
|
|
||||||
private final PlatformTransactionManager transactionManager;
|
|
||||||
private final ContextAware contextAware;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*
|
|
||||||
* @param targetFilterQueryManagement to get all target filter queries
|
|
||||||
* @param deploymentManagement to assign distribution sets to targets
|
|
||||||
* @param transactionManager to run transactions
|
|
||||||
* @param contextAware to handle the context
|
|
||||||
*/
|
|
||||||
protected AbstractAutoAssignExecutor(
|
|
||||||
final TargetFilterQueryManagement<? extends TargetFilterQuery> targetFilterQueryManagement,
|
|
||||||
final DeploymentManagement deploymentManagement,
|
|
||||||
final PlatformTransactionManager transactionManager, final ContextAware contextAware) {
|
|
||||||
this.targetFilterQueryManagement = targetFilterQueryManagement;
|
|
||||||
this.deploymentManagement = deploymentManagement;
|
|
||||||
this.transactionManager = transactionManager;
|
|
||||||
this.contextAware = contextAware;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static String getAutoAssignmentInitiatedBy(final TargetFilterQuery targetFilterQuery) {
|
|
||||||
return StringUtils.hasText(targetFilterQuery.getAutoAssignInitiatedBy())
|
|
||||||
? targetFilterQuery.getAutoAssignInitiatedBy()
|
|
||||||
: targetFilterQuery.getCreatedBy();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected DeploymentManagement getDeploymentManagement() {
|
|
||||||
return deploymentManagement;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected PlatformTransactionManager getTransactionManager() {
|
|
||||||
return transactionManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected TenantAware getContextAware() {
|
|
||||||
return contextAware;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
protected void forEachFilterWithAutoAssignDS(final Consumer<TargetFilterQuery> consumer) {
|
|
||||||
Slice<TargetFilterQuery> filterQueries;
|
|
||||||
Pageable query = PageRequest.of(0, PAGE_SIZE);
|
|
||||||
|
|
||||||
do {
|
|
||||||
filterQueries = targetFilterQueryManagement.findWithAutoAssignDS(query);
|
|
||||||
|
|
||||||
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);
|
|
||||||
return null;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} catch (final RuntimeException ex) {
|
|
||||||
log.debug(
|
|
||||||
"Exception on forEachFilterWithAutoAssignDS execution for tenant {} with filter id {}. Continue with next filter query.",
|
|
||||||
filterQuery.getTenant(), filterQuery.getId(), ex);
|
|
||||||
log.error(
|
|
||||||
"Exception on forEachFilterWithAutoAssignDS execution for tenant {} with filter id {} and error message [{}]. "
|
|
||||||
+ "Continue with next filter query.",
|
|
||||||
filterQuery.getTenant(), filterQuery.getId(), ex.getMessage());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} while ((query = filterQueries.nextPageable()) != Pageable.unpaged());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Runs target assignments within a dedicated transaction for a given list of
|
|
||||||
* controllerIDs
|
|
||||||
*
|
|
||||||
* @param targetFilterQuery the target filter query
|
|
||||||
* @param controllerIds the controllerIDs
|
|
||||||
* @return count of targets
|
|
||||||
*/
|
|
||||||
protected int runTransactionalAssignment(final TargetFilterQuery targetFilterQuery,
|
|
||||||
final List<String> controllerIds) {
|
|
||||||
final String actionMessage = String.format(ACTION_MESSAGE, targetFilterQuery.getName());
|
|
||||||
|
|
||||||
return DeploymentHelper.runInNewTransaction(getTransactionManager(), "autoAssignDSToTargets",
|
|
||||||
Isolation.READ_COMMITTED.value(), status -> {
|
|
||||||
|
|
||||||
final List<DeploymentRequest> deploymentRequests = mapToDeploymentRequests(controllerIds,
|
|
||||||
targetFilterQuery);
|
|
||||||
|
|
||||||
final int count = deploymentRequests.size();
|
|
||||||
if (count > 0) {
|
|
||||||
getDeploymentManagement().assignDistributionSets(
|
|
||||||
getAutoAssignmentInitiatedBy(targetFilterQuery), deploymentRequests, actionMessage);
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a list of {@link DeploymentRequest} for given list of controllerIds
|
|
||||||
* and {@link TargetFilterQuery}
|
|
||||||
*
|
|
||||||
* @param controllerIds list of controllerIds
|
|
||||||
* @param filterQuery the query the targets have to match
|
|
||||||
* @return list of deployment request
|
|
||||||
*/
|
|
||||||
protected List<DeploymentRequest> mapToDeploymentRequests(final List<String> controllerIds,
|
|
||||||
final TargetFilterQuery filterQuery) {
|
|
||||||
// the action type is set to FORCED per default (when not explicitly
|
|
||||||
// specified)
|
|
||||||
final Action.ActionType autoAssignActionType = filterQuery.getAutoAssignActionType() == null
|
|
||||||
? Action.ActionType.FORCED
|
|
||||||
: filterQuery.getAutoAssignActionType();
|
|
||||||
|
|
||||||
return controllerIds.stream()
|
|
||||||
.map(controllerId -> DeploymentRequest
|
|
||||||
.builder(controllerId, filterQuery.getAutoAssignDistributionSet().getId())
|
|
||||||
.actionType(autoAssignActionType).weight(filterQuery.getAutoAssignWeight().orElse(null))
|
|
||||||
.confirmationRequired(filterQuery.isConfirmationRequired()).build())
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,6 +11,7 @@ package org.eclipse.hawkbit.repository.jpa.autoassign;
|
|||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import jakarta.persistence.PersistenceException;
|
import jakarta.persistence.PersistenceException;
|
||||||
|
|
||||||
@@ -20,109 +21,195 @@ import org.eclipse.hawkbit.exception.AbstractServerRtException;
|
|||||||
import org.eclipse.hawkbit.repository.DeploymentManagement;
|
import org.eclipse.hawkbit.repository.DeploymentManagement;
|
||||||
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
||||||
import org.eclipse.hawkbit.repository.TargetManagement;
|
import org.eclipse.hawkbit.repository.TargetManagement;
|
||||||
|
import org.eclipse.hawkbit.repository.autoassign.AutoAssignExecutor;
|
||||||
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
|
import org.eclipse.hawkbit.repository.jpa.configuration.Constants;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.utils.DeploymentHelper;
|
||||||
|
import org.eclipse.hawkbit.repository.model.Action;
|
||||||
|
import org.eclipse.hawkbit.repository.model.DeploymentRequest;
|
||||||
import org.eclipse.hawkbit.repository.model.Target;
|
import org.eclipse.hawkbit.repository.model.Target;
|
||||||
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
|
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
|
||||||
import org.springframework.data.domain.PageRequest;
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.domain.Slice;
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
|
import org.springframework.transaction.annotation.Isolation;
|
||||||
import org.springframework.transaction.annotation.Propagation;
|
import org.springframework.transaction.annotation.Propagation;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if targets need a new distribution set (DS) based on the target filter
|
* Checks if targets need a new distribution set (DS) based on the target filter queries and assigns the new DS when necessary. First all target
|
||||||
* queries and assigns the new DS when necessary. First all target filter
|
* filter queries are listed. For every target filter query (TFQ) the auto assign DS is retrieved. All targets get listed per target filter
|
||||||
* queries are listed. For every target filter query (TFQ) the auto assign DS is
|
* query, that match the TFQ and that don't have the auto assign DS in their action history.
|
||||||
* retrieved. All targets get listed per target filter query, that match the TFQ
|
|
||||||
* and that don't have the auto assign DS in their action history.
|
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class AutoAssignChecker extends AbstractAutoAssignExecutor {
|
public class AutoAssignChecker implements AutoAssignExecutor {
|
||||||
|
|
||||||
private final TargetManagement<? extends Target> targetManagement;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new auto assign checker
|
* The message which is added to the action status when a distribution set is assigned to a target.
|
||||||
*
|
* First %s is the name of the target filter.
|
||||||
* @param targetFilterQueryManagement to get all target filter queries
|
|
||||||
* @param targetManagement to get targets
|
|
||||||
* @param deploymentManagement to assign distribution sets to targets
|
|
||||||
* @param transactionManager to run transactions
|
|
||||||
* @param contextAware to handle the context
|
|
||||||
*/
|
*/
|
||||||
|
private static final String ACTION_MESSAGE = "Auto assignment by target filter: %s";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum for target filter queries with auto assign DS activated.
|
||||||
|
*/
|
||||||
|
private static final int PAGE_SIZE = 1000;
|
||||||
|
|
||||||
|
private final TargetFilterQueryManagement<? extends TargetFilterQuery> targetFilterQueryManagement;
|
||||||
|
private final TargetManagement<? extends Target> targetManagement;
|
||||||
|
private final DeploymentManagement deploymentManagement;
|
||||||
|
private final PlatformTransactionManager transactionManager;
|
||||||
|
private final ContextAware contextAware;
|
||||||
|
|
||||||
public AutoAssignChecker(
|
public AutoAssignChecker(
|
||||||
final TargetFilterQueryManagement<? extends TargetFilterQuery> targetFilterQueryManagement,
|
final TargetFilterQueryManagement<? extends TargetFilterQuery> targetFilterQueryManagement,
|
||||||
final TargetManagement<? extends Target> targetManagement, final DeploymentManagement deploymentManagement,
|
final TargetManagement<? extends Target> targetManagement, final DeploymentManagement deploymentManagement,
|
||||||
final PlatformTransactionManager transactionManager, final ContextAware contextAware) {
|
final PlatformTransactionManager transactionManager, final ContextAware contextAware) {
|
||||||
super(targetFilterQueryManagement, deploymentManagement, transactionManager, contextAware);
|
this.targetFilterQueryManagement = targetFilterQueryManagement;
|
||||||
this.targetManagement = targetManagement;
|
this.targetManagement = targetManagement;
|
||||||
|
this.deploymentManagement = deploymentManagement;
|
||||||
|
this.transactionManager = transactionManager;
|
||||||
|
this.contextAware = contextAware;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
@Transactional(propagation = Propagation.REQUIRES_NEW)
|
||||||
public void checkAllTargets() {
|
public void checkAllTargets() {
|
||||||
log.debug("Auto assign check call for tenant {} started", getContextAware().getCurrentTenant());
|
log.debug("Auto assign check call started");
|
||||||
forEachFilterWithAutoAssignDS(this::checkByTargetFilterQueryAndAssignDS);
|
forEachFilterWithAutoAssignDS(this::checkByTargetFilterQueryAndAssignDS);
|
||||||
log.debug("Auto assign check call for tenant {} finished", getContextAware().getCurrentTenant());
|
log.debug("Auto assign check call finished");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void checkSingleTarget(String controllerId) {
|
public void checkSingleTarget(String controllerId) {
|
||||||
log.debug("Auto assign check call for tenant {} and device {} started", getContextAware().getCurrentTenant(), controllerId);
|
log.debug("Auto assign check call for device {} started", controllerId);
|
||||||
forEachFilterWithAutoAssignDS(filter -> checkForDevice(controllerId, filter));
|
forEachFilterWithAutoAssignDS(filter -> checkForDevice(controllerId, filter));
|
||||||
log.debug("Auto assign check call for tenant {} and device {} finished", getContextAware().getCurrentTenant(), controllerId);
|
log.debug("Auto assign check call for device {} finished", controllerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the distribution set, gets all controllerIds and assigns the DS to
|
* Fetches the distribution set, gets all controllerIds and assigns the DS to them. Catches PersistenceException and own exceptions derived
|
||||||
* them. Catches PersistenceException and own exceptions derived from
|
* from AbstractServerRtException
|
||||||
* AbstractServerRtException
|
|
||||||
*
|
*
|
||||||
* @param targetFilterQuery the target filter query
|
* @param targetFilterQuery the target filter query
|
||||||
*/
|
*/
|
||||||
private void checkByTargetFilterQueryAndAssignDS(final TargetFilterQuery targetFilterQuery) {
|
private void checkByTargetFilterQueryAndAssignDS(final TargetFilterQuery targetFilterQuery) {
|
||||||
log.debug("Auto assign check call for tenant {} and target filter query id {} started",
|
log.debug("Auto assign check call for target filter query id {} started", targetFilterQuery.getId());
|
||||||
getContextAware().getCurrentTenant(), targetFilterQuery.getId());
|
|
||||||
try {
|
try {
|
||||||
int count;
|
int count;
|
||||||
do {
|
do {
|
||||||
final List<String> controllerIds = targetManagement
|
final List<String> controllerIds = targetManagement
|
||||||
.findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(
|
.findByTargetFilterQueryAndNonDSAndCompatibleAndUpdatable(
|
||||||
targetFilterQuery.getAutoAssignDistributionSet().getId(), targetFilterQuery.getQuery(),
|
targetFilterQuery.getAutoAssignDistributionSet().getId(), targetFilterQuery.getQuery(),
|
||||||
PageRequest.of(0, Constants.MAX_ENTRIES_IN_STATEMENT)
|
PageRequest.of(0, Constants.MAX_ENTRIES_IN_STATEMENT))
|
||||||
)
|
|
||||||
.getContent().stream().map(Target::getControllerId).toList();
|
.getContent().stream().map(Target::getControllerId).toList();
|
||||||
log.debug(
|
log.debug("Retrieved {} auto assign targets for target filter query id {}, starting with assignment",
|
||||||
"Retrieved {} auto assign targets for tenant {} and target filter query id {}, starting with assignment",
|
controllerIds.size(), targetFilterQuery.getId());
|
||||||
controllerIds.size(), getContextAware().getCurrentTenant(), targetFilterQuery.getId());
|
|
||||||
|
|
||||||
count = runTransactionalAssignment(targetFilterQuery, controllerIds);
|
count = runTransactionalAssignment(targetFilterQuery, controllerIds);
|
||||||
log.debug(
|
log.debug("Assignment for {} auto assign targets for target filter query id {} finished",
|
||||||
"Assignment for {} auto assign targets for tenant {} and target filter query id {} finished",
|
controllerIds.size(), targetFilterQuery.getId());
|
||||||
controllerIds.size(), getContextAware().getCurrentTenant(), targetFilterQuery.getId());
|
|
||||||
} while (count == Constants.MAX_ENTRIES_IN_STATEMENT);
|
} while (count == Constants.MAX_ENTRIES_IN_STATEMENT);
|
||||||
} catch (final PersistenceException | AbstractServerRtException e) {
|
} catch (final PersistenceException | AbstractServerRtException e) {
|
||||||
log.error("Error during auto assign check of target filter query id {}", targetFilterQuery.getId(), e);
|
log.error("Error during auto assign check of target filter query id {}", targetFilterQuery.getId(), e);
|
||||||
}
|
}
|
||||||
log.debug("Auto assign check call for tenant {} and target filter query id {} finished",
|
log.debug("Auto assign check call for target filter query id {} finished", targetFilterQuery.getId());
|
||||||
getContextAware().getCurrentTenant(), targetFilterQuery.getId());
|
}
|
||||||
|
|
||||||
|
private static String getAutoAssignmentInitiatedBy(final TargetFilterQuery targetFilterQuery) {
|
||||||
|
return StringUtils.hasText(targetFilterQuery.getAutoAssignInitiatedBy())
|
||||||
|
? targetFilterQuery.getAutoAssignInitiatedBy()
|
||||||
|
: targetFilterQuery.getCreatedBy();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
private void forEachFilterWithAutoAssignDS(final Consumer<TargetFilterQuery> consumer) {
|
||||||
|
Slice<TargetFilterQuery> filterQueries;
|
||||||
|
Pageable query = PageRequest.of(0, PAGE_SIZE);
|
||||||
|
do {
|
||||||
|
filterQueries = targetFilterQueryManagement.findWithAutoAssignDS(query);
|
||||||
|
|
||||||
|
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);
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} catch (final RuntimeException ex) {
|
||||||
|
if (log.isDebugEnabled()) {
|
||||||
|
log.debug("Exception on forEachFilterWithAutoAssignDS execution for filter id {}. Continue with next filter query.",
|
||||||
|
filterQuery.getId(), ex);
|
||||||
|
} else {
|
||||||
|
log.error(
|
||||||
|
"Exception on forEachFilterWithAutoAssignDS execution for filter id {} and error message [{}]. Continue with next filter query.",
|
||||||
|
filterQuery.getId(), ex.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} while (filterQueries.hasNext() && (query = filterQueries.nextPageable()) != Pageable.unpaged());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs target assignments within a dedicated transaction for a given list of controllerIDs
|
||||||
|
*
|
||||||
|
* @param targetFilterQuery the target filter query
|
||||||
|
* @param controllerIds the controllerIDs
|
||||||
|
* @return count of targets
|
||||||
|
*/
|
||||||
|
private int runTransactionalAssignment(final TargetFilterQuery targetFilterQuery, final List<String> controllerIds) {
|
||||||
|
final String actionMessage = String.format(ACTION_MESSAGE, targetFilterQuery.getName());
|
||||||
|
return DeploymentHelper.runInNewTransaction(transactionManager, "autoAssignDSToTargets", Isolation.READ_COMMITTED.value(), status -> {
|
||||||
|
final List<DeploymentRequest> deploymentRequests = mapToDeploymentRequests(controllerIds, targetFilterQuery);
|
||||||
|
final int count = deploymentRequests.size();
|
||||||
|
if (count > 0) {
|
||||||
|
deploymentManagement.assignDistributionSets(getAutoAssignmentInitiatedBy(targetFilterQuery), deploymentRequests, actionMessage);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a list of {@link DeploymentRequest} for given list of controllerIds and {@link TargetFilterQuery}
|
||||||
|
*
|
||||||
|
* @param controllerIds list of controllerIds
|
||||||
|
* @param filterQuery the query the targets have to match
|
||||||
|
* @return list of deployment request
|
||||||
|
*/
|
||||||
|
private List<DeploymentRequest> mapToDeploymentRequests(final List<String> controllerIds, final TargetFilterQuery filterQuery) {
|
||||||
|
// the action type is set to FORCED per default (when not explicitly specified)
|
||||||
|
final Action.ActionType autoAssignActionType = filterQuery.getAutoAssignActionType() == null
|
||||||
|
? Action.ActionType.FORCED
|
||||||
|
: filterQuery.getAutoAssignActionType();
|
||||||
|
return controllerIds.stream()
|
||||||
|
.map(controllerId -> DeploymentRequest
|
||||||
|
.builder(controllerId, filterQuery.getAutoAssignDistributionSet().getId())
|
||||||
|
.actionType(autoAssignActionType).weight(filterQuery.getAutoAssignWeight().orElse(null))
|
||||||
|
.confirmationRequired(filterQuery.isConfirmationRequired()).build())
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkForDevice(final String controllerId, final TargetFilterQuery targetFilterQuery) {
|
private void checkForDevice(final String controllerId, final TargetFilterQuery targetFilterQuery) {
|
||||||
log.debug("Auto assign check call for tenant {} and target filter query id {} for device {} started",
|
log.debug("Auto assign check call for target filter query id {} for device {} started", targetFilterQuery.getId(), controllerId);
|
||||||
getContextAware().getCurrentTenant(), targetFilterQuery.getId(), controllerId);
|
|
||||||
try {
|
try {
|
||||||
final boolean controllerIdMatches = targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(
|
if (targetManagement.isTargetMatchingQueryAndDSNotAssignedAndCompatibleAndUpdatable(
|
||||||
controllerId, targetFilterQuery.getAutoAssignDistributionSet().getId(),
|
controllerId, targetFilterQuery.getAutoAssignDistributionSet().getId(), targetFilterQuery.getQuery())) {
|
||||||
targetFilterQuery.getQuery());
|
|
||||||
|
|
||||||
if (controllerIdMatches) {
|
|
||||||
runTransactionalAssignment(targetFilterQuery, Collections.singletonList(controllerId));
|
runTransactionalAssignment(targetFilterQuery, Collections.singletonList(controllerId));
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (final PersistenceException | AbstractServerRtException e) {
|
} catch (final PersistenceException | AbstractServerRtException e) {
|
||||||
log.error("Error during auto assign check of target filter query id {}", targetFilterQuery.getId(), e);
|
log.error("Error during auto assign check of target filter query id {}", targetFilterQuery.getId(), e);
|
||||||
}
|
}
|
||||||
log.debug("Auto assign check call for tenant {} and target filter query id {} for device {} finished",
|
log.debug("Auto assign check call for target filter query id {} for device {} finished", targetFilterQuery.getId(), controllerId);
|
||||||
getContextAware().getCurrentTenant(), targetFilterQuery.getId(), controllerId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* 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.jpa.acm;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
|
||||||
|
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;
|
||||||
|
import org.eclipse.hawkbit.repository.model.Target;
|
||||||
|
import org.eclipse.hawkbit.repository.model.TargetType;
|
||||||
|
import org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch;
|
||||||
|
import org.eclipse.hawkbit.repository.test.util.WithUser;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.springframework.test.context.TestPropertySource;
|
||||||
|
|
||||||
|
@TestPropertySource(properties = "hawkbit.acm.access-controller.enabled=true")
|
||||||
|
abstract class AbstractAccessControllerTest extends AbstractJpaIntegrationTest {
|
||||||
|
|
||||||
|
protected SoftwareModuleType smType1;
|
||||||
|
protected SoftwareModuleType smType2;
|
||||||
|
protected SoftwareModule sm1Type1;
|
||||||
|
protected SoftwareModule sm2Type2;
|
||||||
|
protected SoftwareModule sm3Type2;
|
||||||
|
|
||||||
|
protected DistributionSetType dsType1;
|
||||||
|
protected DistributionSetType dsType2;
|
||||||
|
protected DistributionSet ds1Type1;
|
||||||
|
protected DistributionSet ds2Type2;
|
||||||
|
protected DistributionSet ds3Type2;
|
||||||
|
|
||||||
|
protected TargetType targetType1;
|
||||||
|
protected TargetType targetType2;
|
||||||
|
protected Target target1Type1;
|
||||||
|
protected Target target2Type2;
|
||||||
|
protected Target target3Type2;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
@Override
|
||||||
|
public void beforeAll() throws Exception {
|
||||||
|
super.beforeAll();
|
||||||
|
|
||||||
|
smType1 = testdataFactory.findOrCreateSoftwareModuleType("SmType1");
|
||||||
|
smType2 = testdataFactory.findOrCreateSoftwareModuleType("SmType2");
|
||||||
|
sm1Type1 = softwareModuleManagement.lock(testdataFactory.createSoftwareModule(smType1.getKey()));
|
||||||
|
sm2Type2 = softwareModuleManagement.lock(testdataFactory.createSoftwareModule(smType2.getKey()));
|
||||||
|
sm3Type2 = softwareModuleManagement.lock(testdataFactory.createSoftwareModule(smType2.getKey()));
|
||||||
|
|
||||||
|
dsType1 = testdataFactory.findOrCreateDistributionSetType("DsType1", "DistributionSetType-1", List.of(smType1), List.of());
|
||||||
|
dsType2 = testdataFactory.findOrCreateDistributionSetType("DsType2", "DistributionSetType-2", List.of(smType2), List.of(smType1));
|
||||||
|
ds1Type1 = distributionSetManagement.lock(
|
||||||
|
testdataFactory.createDistributionSet("Ds1Type1", "1.0", dsType1, List.of(sm1Type1)));
|
||||||
|
ds2Type2 = distributionSetManagement.lock(
|
||||||
|
testdataFactory.createDistributionSet("Ds2Type2", "1.0", dsType2, List.of(sm2Type2, sm1Type1)));
|
||||||
|
ds3Type2 = distributionSetManagement.lock(
|
||||||
|
testdataFactory.createDistributionSet("Ds3Type2", "1.0", dsType2, List.of(sm3Type2, sm1Type1)));
|
||||||
|
|
||||||
|
targetType1 = testdataFactory.createTargetType("TargetType1", Set.of(dsType1, dsType2));
|
||||||
|
targetType2 = testdataFactory.createTargetType("TargetType2", Set.of(dsType2));
|
||||||
|
target1Type1 = testdataFactory.createTarget("controller_1", "Controller-1", targetType1);
|
||||||
|
target2Type2 = testdataFactory.createTarget("controller_2", "Controller-2", targetType2);
|
||||||
|
target3Type2 = testdataFactory.createTarget("controller_3", "Controller-3", targetType2);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static WithUser withAuthorities(final String... authorities) {
|
||||||
|
AuthorityChecker.validateAuthorities(authorities);
|
||||||
|
return SecurityContextSwitch.withUser("user", authorities);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,25 +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.jpa.acm;
|
|
||||||
|
|
||||||
import org.eclipse.hawkbit.security.SecurityContextSerializer;
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
class AcmTestConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@ConditionalOnMissingBean
|
|
||||||
SecurityContextSerializer securityContextSerializer() {
|
|
||||||
return SecurityContextSerializer.JSON_SERIALIZATION;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -27,7 +27,6 @@ import org.junit.jupiter.api.Test;
|
|||||||
import org.springframework.test.context.ContextConfiguration;
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
import org.springframework.test.context.TestPropertySource;
|
import org.springframework.test.context.TestPropertySource;
|
||||||
|
|
||||||
@ContextConfiguration(classes = { AccessControllerConfiguration.class })
|
|
||||||
@TestPropertySource(properties = "hawkbit.acm.access-controller.enabled=true")
|
@TestPropertySource(properties = "hawkbit.acm.access-controller.enabled=true")
|
||||||
class ActionAccessControllerTest extends AbstractJpaIntegrationTest {
|
class ActionAccessControllerTest extends AbstractJpaIntegrationTest {
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
* 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.jpa.acm;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.eclipse.hawkbit.im.authentication.SpPermission.CREATE_TARGET;
|
||||||
|
import static org.eclipse.hawkbit.im.authentication.SpPermission.DELETE_TARGET;
|
||||||
|
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_DISTRIBUTION_SET;
|
||||||
|
import static org.eclipse.hawkbit.im.authentication.SpPermission.READ_TARGET;
|
||||||
|
import static org.eclipse.hawkbit.im.authentication.SpPermission.UPDATE_TARGET;
|
||||||
|
import static org.eclipse.hawkbit.repository.test.util.SecurityContextSwitch.callAs;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.eclipse.hawkbit.repository.Identifiable;
|
||||||
|
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement;
|
||||||
|
import org.eclipse.hawkbit.repository.TargetFilterQueryManagement.AutoAssignDistributionSetUpdate;
|
||||||
|
import org.eclipse.hawkbit.repository.autoassign.AutoAssignExecutor;
|
||||||
|
import org.eclipse.hawkbit.repository.jpa.autoassign.AutoAssignScheduler;
|
||||||
|
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.integration.support.locks.LockRegistry;
|
||||||
|
|
||||||
|
class AutoAssignTest extends AbstractAccessControllerTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
AutoAssignExecutor autoAssignExecutor;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
LockRegistry lockRegistry;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void verifyOnlyUpdatableTargetsArePartOfAutoAssignmentByScheduler() throws Exception {
|
||||||
|
// auto assign scheduler apply stored access control context and the context is correctly applied
|
||||||
|
verifyOnlyUpdatableTargetsArePartOfAutoAssignment(
|
||||||
|
() -> new AutoAssignScheduler(systemManagement, systemSecurityContext, autoAssignExecutor, lockRegistry, Optional.empty())
|
||||||
|
.autoAssignScheduler());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void verifyOnlyUpdatableTargetsArePartOfAutoAssignment() throws Exception {
|
||||||
|
verifyOnlyUpdatableTargetsArePartOfAutoAssignment(autoAssignExecutor::checkAllTargets);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void verifyOnlyUpdatableTargetsWillGetAssignmentOnSingleCheck() throws Exception {
|
||||||
|
verifyOnlyUpdatableTargetsArePartOfAutoAssignment(() -> {
|
||||||
|
autoAssignExecutor.checkSingleTarget(target1Type1.getControllerId());
|
||||||
|
autoAssignExecutor.checkSingleTarget(target2Type2.getControllerId());
|
||||||
|
autoAssignExecutor.checkSingleTarget(target3Type2.getControllerId());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyOnlyUpdatableTargetsArePartOfAutoAssignment(final Runnable assigner) throws Exception {
|
||||||
|
final TargetFilterQuery targetFilterQuery = callAs(withAuthorities(
|
||||||
|
CREATE_TARGET,
|
||||||
|
READ_TARGET + "/controllerid==*",
|
||||||
|
UPDATE_TARGET + "/type.id==" + targetType2.getId(), // only updatable (i.e. of targetType2) shall be assigned
|
||||||
|
DELETE_TARGET + "/type.id==" + targetType1.getId(),
|
||||||
|
READ_DISTRIBUTION_SET + "/type.id==" + dsType2.getId()),
|
||||||
|
() -> {
|
||||||
|
final TargetFilterQuery targetFilter = targetFilterQueryManagement
|
||||||
|
.create(TargetFilterQueryManagement.Create.builder().name("testAutoAssignment").query("controllerid==*").build());
|
||||||
|
return targetFilterQueryManagement.updateAutoAssignDS(
|
||||||
|
new AutoAssignDistributionSetUpdate(targetFilter.getId()).ds(ds2Type2.getId()));
|
||||||
|
});
|
||||||
|
|
||||||
|
// do the assignment
|
||||||
|
assigner.run();
|
||||||
|
|
||||||
|
assertThat(targetManagement.findByAssignedDistributionSet(targetFilterQuery.getAutoAssignDistributionSet().getId(), Pageable.unpaged())
|
||||||
|
.map(Identifiable::getId).toList())
|
||||||
|
.as("Only updatable targets should be part of the rollout")
|
||||||
|
// all targets are distribution set type 2 compatible, but since user has UPDATE_TARGET only for targets of type 2
|
||||||
|
// only target2 and target3 shall be assigned
|
||||||
|
.containsExactly(target2Type2.getId(), target3Type2.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,7 @@ import static org.mockito.ArgumentMatchers.eq;
|
|||||||
import static org.mockito.Mockito.reset;
|
import static org.mockito.Mockito.reset;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.Set;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
@@ -26,11 +26,15 @@ import org.eclipse.hawkbit.repository.autoassign.AutoAssignExecutor;
|
|||||||
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
|
import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
|
||||||
import org.eclipse.hawkbit.repository.model.Rollout;
|
import org.eclipse.hawkbit.repository.model.Rollout;
|
||||||
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
|
import org.eclipse.hawkbit.repository.model.TargetFilterQuery;
|
||||||
|
import org.eclipse.hawkbit.security.SecurityContextSerializer;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
|
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.data.domain.AuditorAware;
|
import org.springframework.data.domain.AuditorAware;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
@@ -43,10 +47,10 @@ import org.springframework.test.context.ContextConfiguration;
|
|||||||
* Feature: Component Tests - Context runner<br/>
|
* Feature: Component Tests - Context runner<br/>
|
||||||
* Story: Test Context Runner
|
* Story: Test Context Runner
|
||||||
*/
|
*/
|
||||||
@ContextConfiguration(classes = { AcmTestConfiguration.class })
|
@ContextConfiguration(classes = { ContextAwareTest.TestConfiguration.class })
|
||||||
class ContextAwareTest extends AbstractJpaIntegrationTest {
|
class ContextAwareTest extends AbstractJpaIntegrationTest {
|
||||||
|
|
||||||
private static final List<String> AUTHORITIES = SpPermission.getAllAuthorities();
|
private static final Set<String> AUTHORITIES = SpPermission.getAllAuthorities();
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
AutoAssignExecutor autoAssignExecutor;
|
AutoAssignExecutor autoAssignExecutor;
|
||||||
@@ -169,4 +173,14 @@ class ContextAwareTest extends AbstractJpaIntegrationTest {
|
|||||||
SecurityContextHolder.clearContext();
|
SecurityContextHolder.clearContext();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
static class TestConfiguration {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnMissingBean
|
||||||
|
SecurityContextSerializer securityContextSerializer() {
|
||||||
|
return SecurityContextSerializer.JSON_SERIALIZATION;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,6 @@ import org.springframework.test.context.TestPropertySource;
|
|||||||
* Feature: Component Tests - Access Control<br/>
|
* Feature: Component Tests - Access Control<br/>
|
||||||
* Story: Test Distribution Set Access Controller
|
* Story: Test Distribution Set Access Controller
|
||||||
*/
|
*/
|
||||||
@ContextConfiguration(classes = { AccessControllerConfiguration.class })
|
|
||||||
@TestPropertySource(properties = "hawkbit.acm.access-controller.enabled=true")
|
@TestPropertySource(properties = "hawkbit.acm.access-controller.enabled=true")
|
||||||
class DistributionSetAccessControllerTest extends AbstractJpaIntegrationTest {
|
class DistributionSetAccessControllerTest extends AbstractJpaIntegrationTest {
|
||||||
|
|
||||||
|
|||||||
@@ -43,14 +43,12 @@ import org.eclipse.hawkbit.repository.model.TargetUpdateStatus;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
|
||||||
import org.springframework.test.context.TestPropertySource;
|
import org.springframework.test.context.TestPropertySource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Feature: Component Tests - Access Control<br/>
|
* Feature: Component Tests - Access Control<br/>
|
||||||
* Story: Test Target Access Controller
|
* Story: Test Target Access Controller
|
||||||
*/
|
*/
|
||||||
@ContextConfiguration(classes = { AccessControllerConfiguration.class, AcmTestConfiguration.class })
|
|
||||||
@TestPropertySource(properties = "hawkbit.acm.access-controller.enabled=true")
|
@TestPropertySource(properties = "hawkbit.acm.access-controller.enabled=true")
|
||||||
class TargetAccessControllerTest extends AbstractJpaIntegrationTest {
|
class TargetAccessControllerTest extends AbstractJpaIntegrationTest {
|
||||||
|
|
||||||
|
|||||||
@@ -30,14 +30,12 @@ import org.eclipse.hawkbit.repository.jpa.AbstractJpaIntegrationTest;
|
|||||||
import org.eclipse.hawkbit.repository.model.TargetType;
|
import org.eclipse.hawkbit.repository.model.TargetType;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.test.context.ContextConfiguration;
|
|
||||||
import org.springframework.test.context.TestPropertySource;
|
import org.springframework.test.context.TestPropertySource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Feature: Component Tests - Access Control<br/>
|
* Feature: Component Tests - Access Control<br/>
|
||||||
* Story: Test Target Type Access Controller
|
* Story: Test Target Type Access Controller
|
||||||
*/
|
*/
|
||||||
@ContextConfiguration(classes = { AccessControllerConfiguration.class })
|
|
||||||
@TestPropertySource(properties = "hawkbit.acm.access-controller.enabled=true")
|
@TestPropertySource(properties = "hawkbit.acm.access-controller.enabled=true")
|
||||||
class TargetTypeAccessControllerTest extends AbstractJpaIntegrationTest {
|
class TargetTypeAccessControllerTest extends AbstractJpaIntegrationTest {
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
@@ -98,6 +99,7 @@ public class TestdataFactory {
|
|||||||
public static final String INVISIBLE_SM_MD_VALUE = "invisibleMetdataValue";
|
public static final String INVISIBLE_SM_MD_VALUE = "invisibleMetdataValue";
|
||||||
|
|
||||||
public static final RandomStringUtils RANDOM_STRING_UTILS = RandomStringUtils.secure();
|
public static final RandomStringUtils RANDOM_STRING_UTILS = RandomStringUtils.secure();
|
||||||
|
public static final AtomicLong COUNTER = new AtomicLong();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* default {@link Target#getControllerId()}.
|
* default {@link Target#getControllerId()}.
|
||||||
@@ -359,10 +361,8 @@ public class TestdataFactory {
|
|||||||
/**
|
/**
|
||||||
* Creates {@link DistributionSet} in repository.
|
* Creates {@link DistributionSet} in repository.
|
||||||
*
|
*
|
||||||
* @param prefix for {@link SoftwareModule}s and {@link DistributionSet}s name,
|
* @param prefix for {@link SoftwareModule}s and {@link DistributionSet}s name, vendor and description.
|
||||||
* vendor and description.
|
* @param version {@link DistributionSet#getVersion()} and {@link SoftwareModule#getVersion()} extended by a random number.
|
||||||
* @param version {@link DistributionSet#getVersion()} and
|
|
||||||
* {@link SoftwareModule#getVersion()} extended by a random number.
|
|
||||||
* @param isRequiredMigrationStep for {@link DistributionSet#isRequiredMigrationStep()}
|
* @param isRequiredMigrationStep for {@link DistributionSet#isRequiredMigrationStep()}
|
||||||
* @param modules for {@link DistributionSet#getModules()}
|
* @param modules for {@link DistributionSet#getModules()}
|
||||||
* @return {@link DistributionSet} entity.
|
* @return {@link DistributionSet} entity.
|
||||||
@@ -395,7 +395,7 @@ public class TestdataFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates {@link DistributionSet}s in repository including three {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} ,
|
* Creates {@link DistributionSet}s in repository including three {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT},
|
||||||
* {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an iterative number and {@link DistributionSet#isRequiredMigrationStep()}
|
* {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an iterative number and {@link DistributionSet#isRequiredMigrationStep()}
|
||||||
* <code>false</code>.
|
* <code>false</code>.
|
||||||
*
|
*
|
||||||
@@ -427,7 +427,7 @@ public class TestdataFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates {@link DistributionSet}s in repository including three {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} ,
|
* Creates {@link DistributionSet}s in repository including three {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT},
|
||||||
* {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an iterative count and {@link DistributionSet#isRequiredMigrationStep()}
|
* {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an iterative count and {@link DistributionSet#isRequiredMigrationStep()}
|
||||||
* <code>false</code>.
|
* <code>false</code>.
|
||||||
*
|
*
|
||||||
@@ -568,7 +568,7 @@ public class TestdataFactory {
|
|||||||
return softwareModuleManagement.create(
|
return softwareModuleManagement.create(
|
||||||
SoftwareModuleManagement.Create.builder()
|
SoftwareModuleManagement.Create.builder()
|
||||||
.type(findOrCreateSoftwareModuleType(typeKey))
|
.type(findOrCreateSoftwareModuleType(typeKey))
|
||||||
.name(prefix + typeKey)
|
.name(prefix + typeKey + "_" + COUNTER.incrementAndGet())
|
||||||
.version(prefix + DEFAULT_VERSION)
|
.version(prefix + DEFAULT_VERSION)
|
||||||
.description(randomDescriptionShort())
|
.description(randomDescriptionShort())
|
||||||
.vendor(DEFAULT_VENDOR)
|
.vendor(DEFAULT_VENDOR)
|
||||||
@@ -617,7 +617,7 @@ public class TestdataFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates {@link DistributionSet}s in repository including three {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT} ,
|
* Creates {@link DistributionSet}s in repository including three {@link SoftwareModule}s of types {@link #SM_TYPE_OS}, {@link #SM_TYPE_RT},
|
||||||
* {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an iterative number and {@link DistributionSet#isRequiredMigrationStep()}
|
* {@link #SM_TYPE_APP} with {@link #DEFAULT_VERSION} followed by an iterative number and {@link DistributionSet#isRequiredMigrationStep()}
|
||||||
* <code>false</code>.
|
* <code>false</code>.
|
||||||
* <p/>
|
* <p/>
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
*/
|
*/
|
||||||
package org.eclipse.hawkbit.im.authentication;
|
package org.eclipse.hawkbit.im.authentication;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
@@ -124,8 +124,11 @@ public final class SpPermission {
|
|||||||
TENANT_CONFIGURATION + IMPLY + READ_GATEWAY_SECURITY_TOKEN + LINE_BREAK;
|
TENANT_CONFIGURATION + IMPLY + READ_GATEWAY_SECURITY_TOKEN + LINE_BREAK;
|
||||||
|
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
private static final SingletonSupplier<List<String>> ALL_AUTHORITIES = SingletonSupplier.of(() -> {
|
private static final SingletonSupplier<Set<String>> ALL_AUTHORITIES = SingletonSupplier.of(() -> getAuthorities(false));
|
||||||
final List<String> allPermissions = new ArrayList<>();
|
private static final SingletonSupplier<Set<String>> ALL_TENANT_AUTHORITIES = SingletonSupplier.of(() -> getAuthorities(true));
|
||||||
|
|
||||||
|
private static Set<String> getAuthorities(final boolean tenant) {
|
||||||
|
final Set<String> allPermissions = new HashSet<>();
|
||||||
|
|
||||||
// groups with access, canonical
|
// groups with access, canonical
|
||||||
for (final String group : new String[] {
|
for (final String group : new String[] {
|
||||||
@@ -150,18 +153,19 @@ public final class SpPermission {
|
|||||||
}
|
}
|
||||||
allPermissions.add(TENANT_CONFIGURATION);
|
allPermissions.add(TENANT_CONFIGURATION);
|
||||||
|
|
||||||
|
if (!tenant) {
|
||||||
// system permission, (!) take care with
|
// system permission, (!) take care with
|
||||||
allPermissions.add(SYSTEM_ADMIN);
|
allPermissions.add(SYSTEM_ADMIN);
|
||||||
|
}
|
||||||
|
|
||||||
return Collections.unmodifiableList(allPermissions);
|
return Collections.unmodifiableSet(allPermissions);
|
||||||
});
|
}
|
||||||
|
|
||||||
/**
|
public static Set<String> getAllAuthorities() {
|
||||||
* Return all permission.
|
|
||||||
*
|
|
||||||
* @return all permissions
|
|
||||||
*/
|
|
||||||
public static List<String> getAllAuthorities() {
|
|
||||||
return ALL_AUTHORITIES.get();
|
return ALL_AUTHORITIES.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Set<String> getAllTenantAuthorities() {
|
||||||
|
return ALL_TENANT_AUTHORITIES.get();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -12,8 +12,9 @@ package org.eclipse.hawkbit.security;
|
|||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.eclipse.hawkbit.security.SecurityContextSerializer.JSON_SERIALIZATION;
|
import static org.eclipse.hawkbit.security.SecurityContextSerializer.JSON_SERIALIZATION;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.eclipse.hawkbit.im.authentication.SpPermission;
|
import org.eclipse.hawkbit.im.authentication.SpPermission;
|
||||||
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
|
import org.eclipse.hawkbit.tenancy.TenantAwareAuthenticationDetails;
|
||||||
@@ -27,7 +28,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
|||||||
|
|
||||||
class SecurityContextSerializerTest {
|
class SecurityContextSerializerTest {
|
||||||
|
|
||||||
private static final List<String> AUTHORITIES = SpPermission.getAllAuthorities();
|
private static final Set<String> AUTHORITIES = SpPermission.getAllAuthorities();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testJsonSerialization() {
|
void testJsonSerialization() {
|
||||||
@@ -42,7 +43,7 @@ class SecurityContextSerializerTest {
|
|||||||
final SecurityContext deserialized = JSON_SERIALIZATION.deserialize(serialized);
|
final SecurityContext deserialized = JSON_SERIALIZATION.deserialize(serialized);
|
||||||
final Authentication authentication = deserialized.getAuthentication();
|
final Authentication authentication = deserialized.getAuthentication();
|
||||||
assertThat(SpringSecurityAuditorAware.resolveAuditor(authentication)).hasToString("user");
|
assertThat(SpringSecurityAuditorAware.resolveAuditor(authentication)).hasToString("user");
|
||||||
assertThat(authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList()).isEqualTo(AUTHORITIES);
|
assertThat(authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet())).isEqualTo(AUTHORITIES);
|
||||||
assertThat(authentication.isAuthenticated()).isTrue();
|
assertThat(authentication.isAuthenticated()).isTrue();
|
||||||
assertThat(authentication.getDetails()).isEqualTo(details);
|
assertThat(authentication.getDetails()).isEqualTo(details);
|
||||||
}
|
}
|
||||||
@@ -75,7 +76,7 @@ class SecurityContextSerializerTest {
|
|||||||
final SecurityContext deserialized = JSON_SERIALIZATION.deserialize(serialized);
|
final SecurityContext deserialized = JSON_SERIALIZATION.deserialize(serialized);
|
||||||
final Authentication authentication = deserialized.getAuthentication();
|
final Authentication authentication = deserialized.getAuthentication();
|
||||||
assertThat(SpringSecurityAuditorAware.resolveAuditor(authentication)).hasToString("user");
|
assertThat(SpringSecurityAuditorAware.resolveAuditor(authentication)).hasToString("user");
|
||||||
assertThat(authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).toList()).isEqualTo(AUTHORITIES);
|
assertThat(authentication.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet())).isEqualTo(AUTHORITIES);
|
||||||
assertThat(authentication.isAuthenticated()).isTrue();
|
assertThat(authentication.isAuthenticated()).isTrue();
|
||||||
assertThat(authentication.getDetails()).isEqualTo(details);
|
assertThat(authentication.getDetails()).isEqualTo(details);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user