diff --git a/pluto-server/src/main/java/de/itvsh/ozg/pluto/attached_item/VorgangAttachedItem.java b/pluto-server/src/main/java/de/itvsh/ozg/pluto/attached_item/VorgangAttachedItem.java index c9b63dabc3753bfa096c75a94a138e5440b2f49c..dabd2b581509e90f334f1213650f0565499fd68f 100644 --- a/pluto-server/src/main/java/de/itvsh/ozg/pluto/attached_item/VorgangAttachedItem.java +++ b/pluto-server/src/main/java/de/itvsh/ozg/pluto/attached_item/VorgangAttachedItem.java @@ -54,7 +54,7 @@ public class VorgangAttachedItem { static final String FIELDNAME_VORGANG_ID = "vorgangId"; static final String FIELDNAME_ITEM_NAME = "itemName"; static final String FIELDNAME_ITEM = "item"; - static final String FIELDNAME_IS_DELETED = "deleted"; + public static final String FIELDNAME_IS_DELETED = "deleted"; @Id private String id; diff --git a/pluto-server/src/main/java/de/itvsh/ozg/pluto/common/security/PolicyRepository.java b/pluto-server/src/main/java/de/itvsh/ozg/pluto/common/security/PolicyRepository.java index 7a63c573f7ea12f77fed22d00db857a6f840b3b1..3e021dfd3ecb0763bdf6641b2bf2bb4f7ddbee42 100644 --- a/pluto-server/src/main/java/de/itvsh/ozg/pluto/common/security/PolicyRepository.java +++ b/pluto-server/src/main/java/de/itvsh/ozg/pluto/common/security/PolicyRepository.java @@ -25,6 +25,8 @@ package de.itvsh.ozg.pluto.common.security; import java.util.Arrays; import java.util.Collection; +import java.util.List; +import java.util.Optional; import org.bson.Document; import org.springframework.beans.factory.annotation.Autowired; @@ -36,6 +38,8 @@ import org.springframework.stereotype.Repository; import de.itvsh.ozg.pluto.attached_item.VorgangAttachedItem; import de.itvsh.ozg.pluto.command.Command; +import de.itvsh.ozg.pluto.common.db.CriteriaUtil; +import de.itvsh.ozg.pluto.common.errorhandling.NotFoundException; import de.itvsh.ozg.pluto.vorgang.Vorgang; @Repository @@ -51,51 +55,75 @@ class PolicyRepository { private static final AggregationOperation ADD_VORGANG_FROM_FILE = context -> org.bson.Document.parse(VORANG_ID_FROM_FILE_AS_OBJECT_ID_FIELD); + public static final String FIELD_ORGANISATIONSEINHEIT_ID = "organisationseinheitenId"; + public static final String FIELD_VORGANG = "vorgang"; + public static final String FIELD_VORGANG_ORGANISATIONSEINHEIT = FIELD_VORGANG + "." + Vorgang.FIELD_ORGANISATIONSEINHEIT; + @Autowired private MongoTemplate template; public boolean existsByCommandId(String commandId, Collection<String> organisationEinheitenIds) { - return evaluateAggregation(buildAggregation(commandId, organisationEinheitenIds, ADD_VORGANG_OBJECT_ID_FIELD), Command.class); + var document = executeAggregation(buildAggregation(matchId(commandId), ADD_VORGANG_OBJECT_ID_FIELD), Command.class).orElseThrow( + () -> new NotFoundException(Command.class, commandId)); + return evaluateOrganisationseinheitId(document, organisationEinheitenIds); + } + + public boolean existsByFileId(String fileId, Collection<String> organisationEinheitenIds) { + var resultDocument = executeAggregation(buildAggregation(matchId(fileId), ADD_VORGANG_FROM_FILE), "fs.files"); + return resultDocument.map(document -> evaluateOrganisationseinheitId(document, organisationEinheitenIds)).orElse(false); + } + + private AggregationOperation matchId(String id) { + return Aggregation.match(new Criteria(Vorgang.MONGODB_FIELDNAME_ID).is(id)); } public boolean existsByVorgangAttachedItem(String vorgangAttachedItemId, Collection<String> organisationEinheitenIds) { - return evaluateAggregation(buildAggregation(vorgangAttachedItemId, organisationEinheitenIds, ADD_VORGANG_OBJECT_ID_FIELD), - VorgangAttachedItem.class); + var result = executeAggregation(buildAggregation(matchVorgangAttachedItemId(vorgangAttachedItemId), ADD_VORGANG_OBJECT_ID_FIELD), + VorgangAttachedItem.class).orElseThrow(() -> new NotFoundException(VorgangAttachedItem.class, vorgangAttachedItemId)); + return evaluateOrganisationseinheitId(result, organisationEinheitenIds); } - public boolean existsByFileId(String fileId, Collection<String> organisationEinheitenIds) { - return evaluateAggregation(buildAggregation(fileId, organisationEinheitenIds, ADD_VORGANG_FROM_FILE), Document.class, "fs.files"); + private AggregationOperation matchVorgangAttachedItemId(String id) { + return Aggregation.match(CriteriaUtil.isId(id).and(VorgangAttachedItem.FIELDNAME_IS_DELETED).is(false)); } - private Aggregation buildAggregation(String objectId, Collection<String> organisationEinheitenIds, AggregationOperation operation) { + private Aggregation buildAggregation(AggregationOperation idAggregation, AggregationOperation operation) { return Aggregation.newAggregation(Arrays.asList( - matchId(objectId), + idAggregation, operation, lookupVorgang(), - matchOrganisationEinheitenId(organisationEinheitenIds))); + Aggregation.project().and(FIELD_VORGANG_ORGANISATIONSEINHEIT).as(FIELD_ORGANISATIONSEINHEIT_ID) + )); } - private boolean evaluateAggregation(Aggregation aggregation, Class<?> givenCollectionClass) { - var foundResult = template.aggregate(aggregation, givenCollectionClass, givenCollectionClass).getMappedResults(); - - return !foundResult.isEmpty(); + private AggregationOperation lookupVorgang() { + return Aggregation.lookup(Vorgang.COLLECTION_NAME, "vorgangObjecId", "_id", FIELD_VORGANG); } - private boolean evaluateAggregation(Aggregation aggregation, Class<?> givenCollectionClass, String collectionName) { - var foundResult = template.aggregate(aggregation, collectionName, givenCollectionClass).getMappedResults(); - - return !foundResult.isEmpty(); + private Optional<Document> executeAggregation(Aggregation aggregation, Class<?> inputType) { + var results = template.aggregate(aggregation, inputType, Document.class).getRawResults(); + return getResult(results); } - private AggregationOperation matchId(String id) { - return Aggregation.match(new Criteria(Vorgang.MONGODB_FIELDNAME_ID).is(id)); + private Optional<Document> executeAggregation(Aggregation aggregation, String collectionName) { + var results = template.aggregate(aggregation, collectionName, Document.class).getRawResults(); + return getResult(results); } - private AggregationOperation lookupVorgang() { - return Aggregation.lookup(Vorgang.COLLECTION_NAME, "vorgangObjecId", "_id", "vorgang"); + Optional<Document> getResult(Document aggregationResult) { + return aggregationResult.getList("results", Document.class).stream().findFirst(); } - private AggregationOperation matchOrganisationEinheitenId(Collection<String> organisationEinheitenIds) { - return Aggregation.match(Criteria.where("vorgang." + Vorgang.FIELD_ORGANISATIONSEINHEIT).in(organisationEinheitenIds)); + boolean evaluateOrganisationseinheitId(Document result, Collection<String> organisationEinheitenIds) { + var nestedList = result.getList(FIELD_ORGANISATIONSEINHEIT_ID, Object.class); + if (nestedList.isEmpty()) { + return false; + } + var organisationseinheitenId = nestedList.get(0); + if (organisationseinheitenId instanceof List<?> orgaIdList) { + return orgaIdList.stream().map(String::valueOf).anyMatch(organisationEinheitenIds::contains); + } + return organisationEinheitenIds.contains(String.valueOf(organisationseinheitenId)); } + } \ No newline at end of file diff --git a/pluto-server/src/main/java/de/itvsh/ozg/pluto/common/security/PolicyService.java b/pluto-server/src/main/java/de/itvsh/ozg/pluto/common/security/PolicyService.java index 8b92bea26dca7f61226b761be755d924727d320d..3d0888066479563a559cb485602221bba362af28 100644 --- a/pluto-server/src/main/java/de/itvsh/ozg/pluto/common/security/PolicyService.java +++ b/pluto-server/src/main/java/de/itvsh/ozg/pluto/common/security/PolicyService.java @@ -81,7 +81,7 @@ public class PolicyService { } public void checkPermissionByVorgangAttachedItem(String vorgangAttachedItemId) { - evaluatePermissions(List.of(hasOrganisationEinheitenIdByVorgangAttachedItem(vorgangAttachedItemId)).stream(), VorgangAttachedItem.class, + evaluatePermissions(Stream.of(hasOrganisationEinheitenIdByVorgangAttachedItem(vorgangAttachedItemId)), VorgangAttachedItem.class, vorgangAttachedItemId); } diff --git a/pluto-server/src/test/java/de/itvsh/ozg/pluto/common/security/PolicyRepositoryITCase.java b/pluto-server/src/test/java/de/itvsh/ozg/pluto/common/security/PolicyRepositoryITCase.java index fd38e7350e6479ea07eff89b5cd7f01a1ff46107..4d258be735870a4703264180406eae5d9ec2e79a 100644 --- a/pluto-server/src/test/java/de/itvsh/ozg/pluto/common/security/PolicyRepositoryITCase.java +++ b/pluto-server/src/test/java/de/itvsh/ozg/pluto/common/security/PolicyRepositoryITCase.java @@ -27,6 +27,7 @@ import static org.assertj.core.api.Assertions.*; import java.util.List; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -39,6 +40,7 @@ import de.itvsh.ozg.pluto.attached_item.VorgangAttachedItem; import de.itvsh.ozg.pluto.attached_item.VorgangAttachedItemTestFactory; import de.itvsh.ozg.pluto.command.Command; import de.itvsh.ozg.pluto.command.CommandTestFactory; +import de.itvsh.ozg.pluto.common.errorhandling.NotFoundException; import de.itvsh.ozg.pluto.files.GridFsTestFactory; import de.itvsh.ozg.pluto.vorgang.Vorgang; import de.itvsh.ozg.pluto.vorgang.VorgangTestFactory; @@ -124,6 +126,19 @@ class PolicyRepositoryITCase { assertThat(result).isFalse(); } + + @Test + void shouldThrowNotFoundIfDeleted() { + var vorgangAttachedItem = mongoOperations.save( + VorgangAttachedItemTestFactory.createBuilder().id(null).deleted(true).version(0).build()); + var id = vorgangAttachedItem.getId(); + + Assertions.assertThrows(NotFoundException.class, () -> existsByVorgangAttachedItem(id)); + } + + private void existsByVorgangAttachedItem(String vorgangAttachedItemId) { + repository.existsByVorgangAttachedItem(vorgangAttachedItemId, List.of(ZustaendigeStelleTestFactory.ORGANISATIONSEINHEIT_ID)); + } } @DisplayName("by file id") diff --git a/pluto-server/src/test/java/de/itvsh/ozg/pluto/common/security/PolicyRepositoryTest.java b/pluto-server/src/test/java/de/itvsh/ozg/pluto/common/security/PolicyRepositoryTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e5b07d32b7af57bd021e1112688d30e6bd6d6122 --- /dev/null +++ b/pluto-server/src/test/java/de/itvsh/ozg/pluto/common/security/PolicyRepositoryTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den + * Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei + * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.itvsh.ozg.pluto.common.security; + +import static org.assertj.core.api.Assertions.*; + +import java.util.List; + +import org.bson.Document; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.springframework.data.mongodb.core.MongoTemplate; + +import de.itvsh.ozg.pluto.vorgang.ZustaendigeStelleTestFactory; + +class PolicyRepositoryTest { + + @InjectMocks + private PolicyRepository repository; + + @Mock + private MongoTemplate mongoTemplate; + + @Nested + class TestEvaluateOrganisationseinheitId { + + @Nested + class TestMatch { + + @Test + void shouldEvaluateVorgang() { + var document = new Document(PolicyRepository.FIELD_ORGANISATIONSEINHEIT_ID, + List.of(List.of(ZustaendigeStelleTestFactory.ORGANISATIONSEINHEIT_ID))); + + var result = repository.evaluateOrganisationseinheitId(document, List.of(ZustaendigeStelleTestFactory.ORGANISATIONSEINHEIT_ID)); + + assertThat(result).isTrue(); + } + + @Test + void shouldEvaluateVorgangStub() { + var document = new Document(PolicyRepository.FIELD_ORGANISATIONSEINHEIT_ID, + List.of(ZustaendigeStelleTestFactory.ORGANISATIONSEINHEIT_ID)); + + var result = repository.evaluateOrganisationseinheitId(document, List.of(ZustaendigeStelleTestFactory.ORGANISATIONSEINHEIT_ID)); + + assertThat(result).isTrue(); + } + } + + @Test + void shouldReturnFalseIfNoMatch() { + var document = new Document(PolicyRepository.FIELD_ORGANISATIONSEINHEIT_ID, + List.of(List.of(ZustaendigeStelleTestFactory.ORGANISATIONSEINHEIT_ID))); + + var result = repository.evaluateOrganisationseinheitId(document, List.of("otherId")); + + assertThat(result).isFalse(); + } + + @Test + void shouldReturnFalseIfEmpty() { + var document = new Document(PolicyRepository.FIELD_ORGANISATIONSEINHEIT_ID, List.of()); + + var result = repository.evaluateOrganisationseinheitId(document, List.of(ZustaendigeStelleTestFactory.ORGANISATIONSEINHEIT_ID)); + + assertThat(result).isFalse(); + } + + @Test + void shouldReturnFalseIfNestedEmpty() { + var document = new Document(PolicyRepository.FIELD_ORGANISATIONSEINHEIT_ID, List.of(List.of())); + + var result = repository.evaluateOrganisationseinheitId(document, List.of(ZustaendigeStelleTestFactory.ORGANISATIONSEINHEIT_ID)); + + assertThat(result).isFalse(); + } + } +} \ No newline at end of file diff --git a/pluto-server/src/test/java/de/itvsh/ozg/pluto/common/security/PolicyServiceTest.java b/pluto-server/src/test/java/de/itvsh/ozg/pluto/common/security/PolicyServiceTest.java index 32fd829a3bd1d903ad7ec0417e95d8ebebc507f3..82cce159ed9c72ff4c714888056e32f3e583bd20 100644 --- a/pluto-server/src/test/java/de/itvsh/ozg/pluto/common/security/PolicyServiceTest.java +++ b/pluto-server/src/test/java/de/itvsh/ozg/pluto/common/security/PolicyServiceTest.java @@ -160,7 +160,7 @@ class PolicyServiceTest { } @Test - void shouldThrowAccessDeniedExceptionOnAllNonMatch() { + void shouldThrowNotFoundException() { when(repository.existsByVorgangAttachedItem(any(), any())).thenReturn(false); assertThrows(AccessDeniedException.class, () -> service.checkPermissionByVorgangAttachedItem(VorgangAttachedItemTestFactory.ID));