Fine grained repository permissions (#2562)

1. Introduce @PrreAuthorize check based on hasPermission - allowing custom processing (compared with non-modifiable hasAuthority/Role processing)
2. Dedicated permissions could be implemented on management api level. Check is made by plugged in PermissionEvaluator
3. Thus common XXX_REPOSITORY permissions could differ for extending services
4. Change create/update entity builder pattern - not via EntityFactory but via clean static lombok based builders (with fine fluent api).
5. Implement abstract repository management jpa class that handles the boilerplate code from extending classes in single place consistently -> AbsreactJpaRepositoryManagement
6. Register management api-s as **Sevice**-s instead of **Bean**-s in order to make easier maintainable and get away from heavy argument forwading
7. Simplify custom hawkbit repository registration + adding proxy to handle exception mapping at lower level - thus not depending on Aspects for converting exceptions
8. Implemented general purpose 'copy' utility (ObjectCopyUtil) that using getter/setter patterns is able to copy (e.g. Create/Update) objects to other objects (e.g. JPA entity objects)
This commit is contained in:
Avgustin Marinov
2025-07-28 14:57:33 +03:00
committed by GitHub
parent 8cdbe54cbe
commit 2b66449ff1
214 changed files with 3456 additions and 4416 deletions

View File

@@ -353,13 +353,13 @@ public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrat
.andExpect(jsonPath(prefix + ".download", equalTo(downloadType)))
.andExpect(jsonPath(prefix + ".update", equalTo(updateType)))
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='jvm')].name",
contains(ds.findFirstModuleByType(runtimeType).get().getName())))
contains(findFirstModuleByType(ds, runtimeType).orElseThrow().getName())))
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='jvm')].version",
contains(ds.findFirstModuleByType(runtimeType).get().getVersion())))
contains(findFirstModuleByType(ds, runtimeType).orElseThrow().getVersion())))
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].name",
contains(ds.findFirstModuleByType(osType).get().getName())))
contains(findFirstModuleByType(ds, osType).orElseThrow().getName())))
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].version",
contains(ds.findFirstModuleByType(osType).get().getVersion())))
contains(findFirstModuleByType(ds, osType).orElseThrow().getVersion())))
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0].size", contains(ARTIFACT_SIZE)))
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='os')].artifacts[0].filename",
contains(artifact.getFilename())))
@@ -391,9 +391,9 @@ public abstract class AbstractDDiApiIntegrationTest extends AbstractRestIntegrat
contains(HTTP_LOCALHOST + tenantAware.getCurrentTenant() + "/controller/v1/" + controllerId +
"/softwaremodules/" + osModuleId + "/artifacts/" + artifactSignature.getFilename() + "/download.MD5SUM")))
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='bApp')].version",
contains(ds.findFirstModuleByType(appType).get().getVersion())))
contains(findFirstModuleByType(ds, appType).orElseThrow().getVersion())))
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='bApp')].metadata").doesNotExist())
.andExpect(jsonPath(prefix + ".chunks[?(@.part=='bApp')].name")
.value(ds.findFirstModuleByType(appType).get().getName()));
.value(findFirstModuleByType(ds, appType).orElseThrow().getName()));
}
}

View File

@@ -83,7 +83,7 @@ class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
final int artifactSize = 5 * 1024;
final byte[] random = nextBytes(artifactSize);
final Artifact artifact = artifactManagement.create(new ArtifactUpload(
new ByteArrayInputStream(random), ds.findFirstModuleByType(osType).get().getId(), "file1", false, artifactSize));
new ByteArrayInputStream(random), findFirstModuleByType(ds, osType).orElseThrow().getId(), "file1", false, artifactSize));
assignDistributionSet(ds, targets);
@@ -178,7 +178,7 @@ class DdiArtifactDownloadTest extends AbstractDDiApiIntegrationTest {
final int artifactSize = (int) quotaManagement.getMaxArtifactSize();
final byte[] random = nextBytes(artifactSize);
final Artifact artifact = artifactManagement.create(new ArtifactUpload(new ByteArrayInputStream(random),
ds.findFirstModuleByType(osType).get().getId(), "file1", false, artifactSize));
findFirstModuleByType(ds, osType).orElseThrow().getId(), "file1", false, artifactSize));
// download fails as artifact is not yet assigned
mvc.perform(get("/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/{filename}",

View File

@@ -123,11 +123,11 @@ class DdiCancelActionTest extends AbstractDDiApiIntegrationTest {
.andExpect(jsonPath("$.deployment.download", equalTo("forced")))
.andExpect(jsonPath("$.deployment.update", equalTo("forced")))
.andExpect(jsonPath("$.deployment.chunks[?(@.part=='jvm')].version",
contains(ds.findFirstModuleByType(runtimeType).get().getVersion())))
contains(findFirstModuleByType(ds, runtimeType).orElseThrow().getVersion())))
.andExpect(jsonPath("$.deployment.chunks[?(@.part=='os')].version",
contains(ds.findFirstModuleByType(osType).get().getVersion())))
contains(findFirstModuleByType(ds, osType).orElseThrow().getVersion())))
.andExpect(jsonPath("$.deployment.chunks[?(@.part=='bApp')].version",
contains(ds.findFirstModuleByType(appType).get().getVersion())));
contains(findFirstModuleByType(ds, appType).orElseThrow().getVersion())));
// and finish it
mvc.perform(post("/{tenant}/controller/v1/" + TestdataFactory.DEFAULT_CONTROLLER_ID + "/deploymentBase/"

View File

@@ -10,7 +10,7 @@
package org.eclipse.hawkbit.ddi.rest.resource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.eclipse.hawkbit.repository.jpa.management.JpaConfirmationManagement.CONFIRMATION_CODE_MSG_PREFIX;
import static org.eclipse.hawkbit.repository.ConfirmationManagement.CONFIRMATION_CODE_MSG_PREFIX;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.hasItem;
@@ -124,7 +124,7 @@ class DdiConfirmationBaseTest extends AbstractDDiApiIntegrationTest {
getAndVerifyConfirmationBasePayload(
DEFAULT_CONTROLLER_ID, MediaType.APPLICATION_JSON, ds, artifact,
artifactSignature, action.getId(),
findDistributionSetByAction.findFirstModuleByType(osType).get().getId(), "forced", "forced");
findFirstModuleByType(findDistributionSetByAction, osType).orElseThrow().getId(), "forced", "forced");
// Retrieved is reported
final Iterable<ActionStatus> actionStatus = deploymentManagement

View File

@@ -189,7 +189,7 @@ class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest {
final DistributionSet findDistributionSetByAction = distributionSetManagement.findByAction(action.getId()).get();
getAndVerifyDeploymentBasePayload(DEFAULT_CONTROLLER_ID, MediaType.APPLICATION_JSON, ds, artifact,
artifactSignature, action.getId(),
findDistributionSetByAction.findFirstModuleByType(osType).get().getId(), "forced", "forced");
findFirstModuleByType(findDistributionSetByAction, osType).orElseThrow().getId(), "forced", "forced");
// Retrieved is reported
final Iterable<ActionStatus> actionStatusMessages = deploymentManagement
@@ -348,10 +348,10 @@ class DdiDeploymentBaseTest extends AbstractDDiApiIntegrationTest {
final DistributionSet findDistributionSetByAction = distributionSetManagement.findByAction(action.getId()).get();
getAndVerifyDeploymentBasePayload(DEFAULT_CONTROLLER_ID, MediaType.APPLICATION_JSON, ds, artifact,
artifactSignature, action.getId(),
findDistributionSetByAction.findFirstModuleByType(osType).get().getId(), "forced", "forced");
findFirstModuleByType(findDistributionSetByAction, osType).orElseThrow().getId(), "forced", "forced");
getAndVerifyDeploymentBasePayload(DEFAULT_CONTROLLER_ID, MediaTypes.HAL_JSON, ds, artifact, artifactSignature,
action.getId(), findDistributionSetByAction.findFirstModuleByType(osType).get().getId(), "forced",
action.getId(), findFirstModuleByType(findDistributionSetByAction, osType).orElseThrow().getId(), "forced",
"forced");
// Retrieved is reported

View File

@@ -160,14 +160,14 @@ class DdiInstalledBaseTest extends AbstractDDiApiIntegrationTest {
startsWith(deploymentBaseLink(CONTROLLER_ID, actionId1.toString()))));
getAndVerifyDeploymentBasePayload(CONTROLLER_ID, MediaType.APPLICATION_JSON, ds1, artifact1, artifactSignature1,
actionId1, ds1.findFirstModuleByType(osType).get().getId(), Action.ActionType.SOFT);
actionId1, findFirstModuleByType(ds1, osType).orElseThrow().getId(), Action.ActionType.SOFT);
postDeploymentFeedback(target.getControllerId(), actionId1,
getJsonActionFeedback(DdiStatus.ExecutionStatus.CLOSED, DdiResult.FinalResult.SUCCESS, Collections.singletonList("Closed")),
status().isOk());
getAndVerifyInstalledBasePayload(CONTROLLER_ID, MediaType.APPLICATION_JSON, ds1, artifact1, artifactSignature1,
actionId1, ds1.findFirstModuleByType(osType).get().getId(), Action.ActionType.SOFT);
actionId1, findFirstModuleByType(ds1, osType).orElseThrow().getId(), Action.ActionType.SOFT);
// Run test with 2nd action
final Long actionId2 = getFirstAssignedActionId(
@@ -180,14 +180,14 @@ class DdiInstalledBaseTest extends AbstractDDiApiIntegrationTest {
startsWith(deploymentBaseLink(CONTROLLER_ID, actionId2.toString()))));
getAndVerifyDeploymentBasePayload(CONTROLLER_ID, MediaType.APPLICATION_JSON, ds2, artifact2, artifactSignature2,
actionId2, ds2.findFirstModuleByType(osType).get().getId(), Action.ActionType.FORCED);
actionId2, findFirstModuleByType(ds2, osType).orElseThrow().getId(), Action.ActionType.FORCED);
postDeploymentFeedback(target.getControllerId(), actionId2,
getJsonActionFeedback(DdiStatus.ExecutionStatus.CLOSED, DdiResult.FinalResult.SUCCESS, Collections.singletonList("Closed")),
status().isOk());
getAndVerifyInstalledBasePayload(CONTROLLER_ID, MediaType.APPLICATION_JSON, ds2, artifact2, artifactSignature2,
actionId2, ds2.findFirstModuleByType(osType).get().getId(), Action.ActionType.FORCED);
actionId2, findFirstModuleByType(ds2, osType).orElseThrow().getId(), Action.ActionType.FORCED);
performGet(CONTROLLER_BASE, MediaTypes.HAL_JSON, status().isOk(), tenantAware.getCurrentTenant(), CONTROLLER_ID)
.andExpect(jsonPath("$.config.polling.sleep", equalTo("00:01:00")))
@@ -197,7 +197,7 @@ class DdiInstalledBaseTest extends AbstractDDiApiIntegrationTest {
// older installed action is still accessible, although not part of controller base
getAndVerifyInstalledBasePayload(CONTROLLER_ID, MediaType.APPLICATION_JSON, ds1, artifact1, artifactSignature1,
actionId1, ds1.findFirstModuleByType(osType).get().getId(), Action.ActionType.SOFT);
actionId1, findFirstModuleByType(ds1, osType).orElseThrow().getId(), Action.ActionType.SOFT);
}
/**
@@ -245,7 +245,7 @@ class DdiInstalledBaseTest extends AbstractDDiApiIntegrationTest {
.andExpect(jsonPath("$._links.deploymentBase.href").doesNotExist());
getAndVerifyInstalledBasePayload(CONTROLLER_ID, MediaType.APPLICATION_JSON, ds1, artifact1, artifactSignature1,
actionId3, ds1.findFirstModuleByType(osType).get().getId(), Action.ActionType.SOFT);
actionId3, findFirstModuleByType(ds1, osType).orElseThrow().getId(), Action.ActionType.SOFT);
// cancelled action are not accessible
mvc.perform(MockMvcRequestBuilders.get(INSTALLED_BASE, tenantAware.getCurrentTenant(), target.getControllerId(),
@@ -304,7 +304,7 @@ class DdiInstalledBaseTest extends AbstractDDiApiIntegrationTest {
.andExpect(jsonPath("$._links.deploymentBase.href").doesNotExist());
getAndVerifyInstalledBasePayload(CONTROLLER_ID, MediaType.APPLICATION_JSON, ds1, artifact1, artifactSignature1,
actionId1, ds1.findFirstModuleByType(osType).get().getId(), Action.ActionType.SOFT);
actionId1, findFirstModuleByType(ds1, osType).orElseThrow().getId(), Action.ActionType.SOFT);
// cancelled action are not accessible
mvc.perform(MockMvcRequestBuilders.get(INSTALLED_BASE, tenantAware.getCurrentTenant(), target.getControllerId(),
@@ -359,7 +359,7 @@ class DdiInstalledBaseTest extends AbstractDDiApiIntegrationTest {
startsWith(deploymentBaseLink(CONTROLLER_ID, actionId3.toString()))));
getAndVerifyInstalledBasePayload(CONTROLLER_ID, MediaType.APPLICATION_JSON, ds1, artifact1, artifactSignature1,
actionId1, ds1.findFirstModuleByType(osType).get().getId(), Action.ActionType.SOFT);
actionId1, findFirstModuleByType(ds1, osType).orElseThrow().getId(), Action.ActionType.SOFT);
// cancelled action are not accessible
mvc.perform(MockMvcRequestBuilders.get(INSTALLED_BASE, tenantAware.getCurrentTenant(), target.getControllerId(),
@@ -463,10 +463,10 @@ class DdiInstalledBaseTest extends AbstractDDiApiIntegrationTest {
tenantAware.getCurrentTenant(), target.getControllerId(), actionId))));
getAndVerifyInstalledBasePayload(CONTROLLER_ID, MediaType.APPLICATION_JSON, ds, artifact, artifactSignature,
actionId, ds.findFirstModuleByType(osType).get().getId(), actionType);
actionId, findFirstModuleByType(ds, osType).orElseThrow().getId(), actionType);
getAndVerifyInstalledBasePayload(CONTROLLER_ID, MediaTypes.HAL_JSON, ds, artifact, artifactSignature, actionId,
ds.findFirstModuleByType(osType).get().getId(), actionType);
findFirstModuleByType(ds, osType).orElseThrow().getId(), actionType);
// Action is still finished after calling installedBase
final Iterable<ActionStatus> actionStatusMessages = deploymentManagement