diff --git a/basyx.aasenvironment/Readme.md b/basyx.aasenvironment/Readme.md index 06e1faf05..96fd375af 100644 --- a/basyx.aasenvironment/Readme.md +++ b/basyx.aasenvironment/Readme.md @@ -38,9 +38,11 @@ Furthermore, if Identifiables (AAS, Submodels, ConceptDescriptions) are already For examples, see [application.properties](./basyx.aasenvironment.component/src/main/resources/application.properties) - ## AAS Environment Upload Endpoint AAS environments (e.g. XML, JSON, AASX) can be uploaded to the `/upload` endpoint. The upload follows the same rules as the preconfiguration in terms of handling existing AAS, submodels and concept descriptions. In order for the file to be recognized correctly, please make sure that its MIME type is properly configured. + +## AAS Environment Features +* [AAS Environment Authorization](basyx.aasenvironment-feature-authorization) \ No newline at end of file diff --git a/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/AasEnvironment.java b/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/AasEnvironment.java index 0e6aaa417..4c8eb637d 100644 --- a/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/AasEnvironment.java +++ b/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/AasEnvironment.java @@ -28,6 +28,7 @@ import java.util.List; import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.SerializationException; +import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.CompleteEnvironment; /** * Specifies the overall AasEnvironment API @@ -51,4 +52,6 @@ public interface AasEnvironment { public String createXMLAASEnvironmentSerialization(List aasIds, List submodelIds, boolean includeConceptDescriptions) throws SerializationException; public byte[] createAASXAASEnvironmentSerialization(List aasIds, List submodelIds, boolean includeConceptDescriptions) throws SerializationException, IOException; + + public void loadEnvironment(CompleteEnvironment completeEnvironment); } diff --git a/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/base/DefaultAASEnvironment.java b/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/base/DefaultAASEnvironment.java index 2a80d8812..f80b3a932 100644 --- a/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/base/DefaultAASEnvironment.java +++ b/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/base/DefaultAASEnvironment.java @@ -24,14 +24,19 @@ ******************************************************************************/ package org.eclipse.digitaltwin.basyx.aasenvironment.base; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import org.apache.commons.io.FilenameUtils; import org.eclipse.digitaltwin.aas4j.v3.dataformat.aasx.AASXSerializer; +import org.eclipse.digitaltwin.aas4j.v3.dataformat.aasx.InMemoryFile; import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.SerializationException; import org.eclipse.digitaltwin.aas4j.v3.dataformat.json.JsonSerializer; import org.eclipse.digitaltwin.aas4j.v3.dataformat.xml.XmlSerializer; @@ -39,10 +44,18 @@ import org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription; import org.eclipse.digitaltwin.aas4j.v3.model.Environment; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultEnvironment; import org.eclipse.digitaltwin.basyx.aasenvironment.AasEnvironment; import org.eclipse.digitaltwin.basyx.aasenvironment.ConceptDescriptionIdCollector; +import org.eclipse.digitaltwin.basyx.aasenvironment.FileElementPathCollector; +import org.eclipse.digitaltwin.basyx.aasenvironment.IdShortPathBuilder; import org.eclipse.digitaltwin.basyx.aasenvironment.MetamodelCloneCreator; +import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.CompleteEnvironment; +import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.IdentifiableUploader; +import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.IdentifiableAssertion; +import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.IdentifiableUploader.DelegatingIdentifiableRepository; +import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.IdentifiableUploader.IdentifiableRepository; import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; @@ -68,6 +81,7 @@ public class DefaultAASEnvironment implements AasEnvironment { private XmlSerializer xmlSerializer = new XmlSerializer(); private AASXSerializer aasxSerializer = new AASXSerializer(); private MetamodelCloneCreator cloneCreator = new MetamodelCloneCreator(); + private IdentifiableAssertion checker = new IdentifiableAssertion(); public DefaultAASEnvironment(AasRepository aasRepository, SubmodelRepository submodelRepository, ConceptDescriptionRepository conceptDescriptionRepository) { this.aasRepository = aasRepository; @@ -96,6 +110,108 @@ public byte[] createAASXAASEnvironmentSerialization(@Valid List aasIds, aasxSerializer.write(aasEnvironment, null, outputStream); return outputStream.toByteArray(); } + + public void loadEnvironment(CompleteEnvironment completeEnvironment) { + Environment environment = completeEnvironment.getEnvironment(); + + if (environment == null) + return; + + checker.assertNoDuplicateIds(environment); + + createShellsOnRepositoryFromEnvironment(environment); + createSubmodelsOnRepositoryFromEnvironment(environment, completeEnvironment.getRelatedFiles()); + createConceptDescriptionsOnRepositoryFromEnvironment(environment); + } + + private void createConceptDescriptionsOnRepositoryFromEnvironment(Environment environment) { + IdentifiableRepository repo = new DelegatingIdentifiableRepository(conceptDescriptionRepository::getConceptDescription, conceptDescriptionRepository::updateConceptDescription, + conceptDescriptionRepository::createConceptDescription); + IdentifiableUploader uploader = new IdentifiableUploader(repo); + for (ConceptDescription conceptDescription : environment.getConceptDescriptions()) { + boolean success = uploader.upload(conceptDescription); + logSuccessConceptDescription(conceptDescription.getId(), success); + } + } + + private void createSubmodelsOnRepositoryFromEnvironment(Environment environment, List relatedFiles) { + List submodels = environment.getSubmodels(); + + createSubmodelsOnRepository(submodels); + + if (relatedFiles == null || relatedFiles.isEmpty()) + return; + + for (Submodel submodel : submodels) { + List> idShortElementPathsOfAllFileSMEs = new FileElementPathCollector(submodel).collect(); + + idShortElementPathsOfAllFileSMEs.stream().forEach(fileSMEIdShortPath -> setFileToFileElement(submodel.getId(), fileSMEIdShortPath, relatedFiles)); + } + } + + private void setFileToFileElement(String submodelId, List fileSMEIdShortPathElements, List relatedFiles) { + String fileSMEIdShortPath = new IdShortPathBuilder(new ArrayList<>(fileSMEIdShortPathElements)).build(); + + org.eclipse.digitaltwin.aas4j.v3.model.File fileSME = (org.eclipse.digitaltwin.aas4j.v3.model.File) submodelRepository.getSubmodelElement(submodelId, fileSMEIdShortPath); + + InMemoryFile inMemoryFile = getAssociatedInMemoryFile(relatedFiles, fileSME.getValue()); + + if (inMemoryFile == null) { + logger.info("Unable to set file to the SubmodelElement File with IdShortPath '{}' because it does not exist in the AASX file.", fileSMEIdShortPath); + + return; + } + + submodelRepository.setFileValue(submodelId, fileSMEIdShortPath, getFileName(inMemoryFile.getPath()), new ByteArrayInputStream(inMemoryFile.getFileContent())); + } + + private String getFileName(String path) { + return FilenameUtils.getName(path); + } + + private InMemoryFile getAssociatedInMemoryFile(List relatedFiles, String value) { + + Optional inMemoryFile = relatedFiles.stream().filter(file -> file.getPath().equals(value)).findAny(); + + if (inMemoryFile.isEmpty()) + return null; + + return inMemoryFile.get(); + } + + private void createShellsOnRepositoryFromEnvironment(Environment environment) { + IdentifiableRepository repo = new DelegatingIdentifiableRepository(aasRepository::getAas, aasRepository::updateAas, aasRepository::createAas); + IdentifiableUploader uploader = new IdentifiableUploader<>(repo); + for (AssetAdministrationShell shell : environment.getAssetAdministrationShells()) { + boolean success = uploader.upload(shell); + logSuccess("shell", shell.getId(), success); + } + } + + private void createSubmodelsOnRepository(List submodels) { + IdentifiableRepository repo = new DelegatingIdentifiableRepository(submodelRepository::getSubmodel, submodelRepository::updateSubmodel, submodelRepository::createSubmodel); + IdentifiableUploader uploader = new IdentifiableUploader<>(repo); + for (Submodel submodel : submodels) { + boolean success = uploader.upload(submodel); + logSuccess("submodel", submodel.getId(), success); + } + } + + private void logSuccess(String resourceName, String id, boolean success) { + if (success) { + logger.info("Uploading " + resourceName + " " + id + " was successful!"); + } else { + logger.warn("Uploading " + resourceName + " " + id + " was not successful!"); + } + } + + private void logSuccessConceptDescription(String conceptDescriptionId, boolean success) { + if (!success) { + logger.warn("Colliding Ids detected for ConceptDescription: " + conceptDescriptionId + ". If they are not identical, this is an error. Please note that the already existing ConceptDescription was not updated."); + } else { + logSuccess("conceptDescription", conceptDescriptionId, success); + } + } private Environment createEnvironment(List aasIds, List submodelIds, boolean includeConceptDescriptions) { List shells = aasIds.stream().map(aasRepository::getAas).collect(Collectors.toList()); diff --git a/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/environmentloader/AasEnvironmentLoader.java b/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/environmentloader/AasEnvironmentLoader.java deleted file mode 100644 index b8e8f6381..000000000 --- a/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/environmentloader/AasEnvironmentLoader.java +++ /dev/null @@ -1,171 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - -package org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader; - -import java.io.ByteArrayInputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import org.apache.commons.io.FilenameUtils; -import org.eclipse.digitaltwin.aas4j.v3.dataformat.aasx.InMemoryFile; -import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; -import org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription; -import org.eclipse.digitaltwin.aas4j.v3.model.Environment; -import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; -import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; -import org.eclipse.digitaltwin.basyx.aasenvironment.FileElementPathCollector; -import org.eclipse.digitaltwin.basyx.aasenvironment.IdShortPathBuilder; -import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.IdentifiableUploader.DelegatingIdentifiableRepository; -import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.IdentifiableUploader.IdentifiableRepository; -import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; -import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository; -import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Loader for AAS Environment - * - * @author fried, mateusmolina, despen, witt, jungjan, danish - * - */ -public class AasEnvironmentLoader { - private Logger logger = LoggerFactory.getLogger(AasEnvironmentLoader.class); - private IndentifiableAssertion checker = new IndentifiableAssertion(); - - private AasRepository aasRepository; - private SubmodelRepository submodelRepository; - private ConceptDescriptionRepository conceptDescriptionRepository; - - public AasEnvironmentLoader(AasRepository aasRepository, SubmodelRepository submodelRepository, ConceptDescriptionRepository conceptDescriptionRepository) { - this.aasRepository = aasRepository; - this.submodelRepository = submodelRepository; - this.conceptDescriptionRepository = conceptDescriptionRepository; - } - - public void loadEnvironment(CompleteEnvironment completeEnvironment) { - Environment environment = completeEnvironment.getEnvironment(); - - if (environment == null) - return; - - checker.assertNoDuplicateIds(environment); - - createShellsOnRepositoryFromEnvironment(environment); - createSubmodelsOnRepositoryFromEnvironment(environment, completeEnvironment.getRelatedFiles()); - createConceptDescriptionsOnRepositoryFromEnvironment(environment); - } - - private void createConceptDescriptionsOnRepositoryFromEnvironment(Environment environment) { - IdentifiableRepository repo = new DelegatingIdentifiableRepository(conceptDescriptionRepository::getConceptDescription, conceptDescriptionRepository::updateConceptDescription, - conceptDescriptionRepository::createConceptDescription); - IdentifiableUploader uploader = new IdentifiableUploader(repo); - for (ConceptDescription conceptDescription : environment.getConceptDescriptions()) { - boolean success = uploader.upload(conceptDescription); - logSuccessConceptDescription(conceptDescription.getId(), success); - } - } - - private void createSubmodelsOnRepositoryFromEnvironment(Environment environment, List relatedFiles) { - List submodels = environment.getSubmodels(); - - createSubmodelsOnRepository(submodels); - - if (relatedFiles == null || relatedFiles.isEmpty()) - return; - - for (Submodel submodel : submodels) { - List> idShortElementPathsOfAllFileSMEs = new FileElementPathCollector(submodel).collect(); - - idShortElementPathsOfAllFileSMEs.stream().forEach(fileSMEIdShortPath -> setFileToFileElement(submodel.getId(), fileSMEIdShortPath, relatedFiles)); - } - } - - private void setFileToFileElement(String submodelId, List fileSMEIdShortPathElements, List relatedFiles) { - String fileSMEIdShortPath = new IdShortPathBuilder(new ArrayList<>(fileSMEIdShortPathElements)).build(); - - org.eclipse.digitaltwin.aas4j.v3.model.File fileSME = (org.eclipse.digitaltwin.aas4j.v3.model.File) submodelRepository.getSubmodelElement(submodelId, fileSMEIdShortPath); - - InMemoryFile inMemoryFile = getAssociatedInMemoryFile(relatedFiles, fileSME.getValue()); - - if (inMemoryFile == null) { - logger.info("Unable to set file to the SubmodelElement File with IdShortPath '{}' because it does not exist in the AASX file.", fileSMEIdShortPath); - - return; - } - - submodelRepository.setFileValue(submodelId, fileSMEIdShortPath, getFileName(inMemoryFile.getPath()), new ByteArrayInputStream(inMemoryFile.getFileContent())); - } - - private String getFileName(String path) { - return FilenameUtils.getName(path); - } - - private InMemoryFile getAssociatedInMemoryFile(List relatedFiles, String value) { - - Optional inMemoryFile = relatedFiles.stream().filter(file -> file.getPath().equals(value)).findAny(); - - if (inMemoryFile.isEmpty()) - return null; - - return inMemoryFile.get(); - } - - private void createShellsOnRepositoryFromEnvironment(Environment environment) { - IdentifiableRepository repo = new DelegatingIdentifiableRepository(aasRepository::getAas, aasRepository::updateAas, aasRepository::createAas); - IdentifiableUploader uploader = new IdentifiableUploader<>(repo); - for (AssetAdministrationShell shell : environment.getAssetAdministrationShells()) { - boolean success = uploader.upload(shell); - logSuccess("shell", shell.getId(), success); - } - } - - private void createSubmodelsOnRepository(List submodels) { - IdentifiableRepository repo = new DelegatingIdentifiableRepository(submodelRepository::getSubmodel, submodelRepository::updateSubmodel, submodelRepository::createSubmodel); - IdentifiableUploader uploader = new IdentifiableUploader<>(repo); - for (Submodel submodel : submodels) { - boolean success = uploader.upload(submodel); - logSuccess("submodel", submodel.getId(), success); - } - } - - private void logSuccess(String resourceName, String id, boolean success) { - if (success) { - logger.info("Uploading " + resourceName + " " + id + " was successful!"); - } else { - logger.warn("Uploading " + resourceName + " " + id + " was not successful!"); - } - } - - private void logSuccessConceptDescription(String conceptDescriptionId, boolean success) { - if (!success) { - logger.warn("Colliding Ids detected for ConceptDescription: " + conceptDescriptionId + ". If they are not identical, this is an error. Please note that the already existing ConceptDescription was not updated."); - } else { - logSuccess("conceptDescription", conceptDescriptionId, success); - } - } -} diff --git a/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/environmentloader/IndentifiableAssertion.java b/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/environmentloader/IdentifiableAssertion.java similarity index 98% rename from basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/environmentloader/IndentifiableAssertion.java rename to basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/environmentloader/IdentifiableAssertion.java index 5da6d44b0..b20eafffa 100644 --- a/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/environmentloader/IndentifiableAssertion.java +++ b/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/environmentloader/IdentifiableAssertion.java @@ -38,7 +38,7 @@ * @author Gerhard Sonnenberg DFKI GmbH * */ -public class IndentifiableAssertion { +public class IdentifiableAssertion { private final Set currentShellIds = new HashSet<>(); private final Set currentSubmodelIds = new HashSet<>(); diff --git a/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/preconfiguration/AasEnvironmentPreconfigurationLoader.java b/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/preconfiguration/AasEnvironmentPreconfigurationLoader.java index 2d4c0040d..559612d0d 100644 --- a/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/preconfiguration/AasEnvironmentPreconfigurationLoader.java +++ b/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/preconfiguration/AasEnvironmentPreconfigurationLoader.java @@ -33,7 +33,7 @@ import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.DeserializationException; -import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.AasEnvironmentLoader; +import org.eclipse.digitaltwin.basyx.aasenvironment.AasEnvironment; import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.CompleteEnvironment; import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.CompleteEnvironment.EnvironmentType; import org.slf4j.Logger; @@ -70,7 +70,7 @@ public boolean shouldLoadPreconfiguredEnvironment() { return pathsToLoad != null; } - public void loadPreconfiguredEnvironments(AasEnvironmentLoader environmentLoader) + public void loadPreconfiguredEnvironments(AasEnvironment aasEnvironment) throws IOException, DeserializationException, InvalidFormatException { List files = scanForEnvironments(pathsToLoad); @@ -82,7 +82,7 @@ public void loadPreconfiguredEnvironments(AasEnvironmentLoader environmentLoader for (File file : files) { logLoadingProcess(currenFileIndex++, filesCount, file.getName()); - environmentLoader.loadEnvironment(CompleteEnvironment.fromFile(file)); + aasEnvironment.loadEnvironment(CompleteEnvironment.fromFile(file)); } } diff --git a/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/preconfiguration/PreconfigurationLoaderInitializer.java b/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/preconfiguration/PreconfigurationLoaderInitializer.java index e4db274e2..e2a354679 100644 --- a/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/preconfiguration/PreconfigurationLoaderInitializer.java +++ b/basyx.aasenvironment/basyx.aasenvironment-core/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/preconfiguration/PreconfigurationLoaderInitializer.java @@ -29,7 +29,7 @@ import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.DeserializationException; -import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.AasEnvironmentLoader; +import org.eclipse.digitaltwin.basyx.aasenvironment.AasEnvironment; import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -43,15 +43,15 @@ @Component public class PreconfigurationLoaderInitializer implements InitializingBean { - private AasEnvironmentLoader environmentLoader; + private AasEnvironment aasEnvironment; private AasEnvironmentPreconfigurationLoader preconfigurationLoader; @Autowired - public PreconfigurationLoaderInitializer(AasEnvironmentLoader environmentLoader, + public PreconfigurationLoaderInitializer(AasEnvironment aasEnvironment, AasEnvironmentPreconfigurationLoader preconfigurationLoader) { super(); - this.environmentLoader = environmentLoader; + this.aasEnvironment = aasEnvironment; this.preconfigurationLoader = preconfigurationLoader; } @@ -65,7 +65,7 @@ private void loadPreconfiguredEnvironment() throws IOException, InvalidFormatExc return; } - preconfigurationLoader.loadPreconfiguredEnvironments(environmentLoader); + preconfigurationLoader.loadPreconfiguredEnvironments(aasEnvironment); } } diff --git a/basyx.aasenvironment/basyx.aasenvironment-core/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/AasEnvironmentLoaderTest.java b/basyx.aasenvironment/basyx.aasenvironment-core/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/AasEnvironmentLoaderTest.java index 20b3d13d8..dc7e8634a 100644 --- a/basyx.aasenvironment/basyx.aasenvironment-core/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/AasEnvironmentLoaderTest.java +++ b/basyx.aasenvironment/basyx.aasenvironment-core/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/AasEnvironmentLoaderTest.java @@ -31,7 +31,7 @@ import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.DeserializationException; -import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.AasEnvironmentLoader; +import org.eclipse.digitaltwin.basyx.aasenvironment.base.DefaultAASEnvironment; import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.CompleteEnvironment; import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; import org.eclipse.digitaltwin.basyx.aasrepository.backend.CrudAasRepository; @@ -54,7 +54,7 @@ /** - * Tests the behavior of {@link AasEnvironmentLoader} + * Tests the behavior of {@link AasEnvironment} loader functionality * * @author sonnenberg, mateusmolina * @@ -83,7 +83,8 @@ public void setUp() { } protected void loadRepositories(List pathsToLoad) throws IOException, DeserializationException, InvalidFormatException { - AasEnvironmentLoader envLoader = new AasEnvironmentLoader(aasRepository, submodelRepository, conceptDescriptionRepository); + DefaultAASEnvironment envLoader = new DefaultAASEnvironment(aasRepository, submodelRepository, conceptDescriptionRepository); + for (String path: pathsToLoad) { File file = rLoader.getResource(path).getFile(); envLoader.loadEnvironment(CompleteEnvironment.fromFile(file)); diff --git a/basyx.aasenvironment/basyx.aasenvironment-core/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/PreconfigurationLoaderTextualResourceTest.java b/basyx.aasenvironment/basyx.aasenvironment-core/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/PreconfigurationLoaderTextualResourceTest.java index 500e1aef4..29a8774c4 100644 --- a/basyx.aasenvironment/basyx.aasenvironment-core/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/PreconfigurationLoaderTextualResourceTest.java +++ b/basyx.aasenvironment/basyx.aasenvironment-core/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/PreconfigurationLoaderTextualResourceTest.java @@ -29,7 +29,7 @@ import org.apache.poi.openxml4j.exceptions.InvalidFormatException; import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.DeserializationException; -import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.AasEnvironmentLoader; +import org.eclipse.digitaltwin.basyx.aasenvironment.base.DefaultAASEnvironment; import org.eclipse.digitaltwin.basyx.aasenvironment.preconfiguration.AasEnvironmentPreconfigurationLoader; import org.junit.Assert; import org.junit.Test; @@ -46,7 +46,7 @@ public class PreconfigurationLoaderTextualResourceTest extends AasEnvironmentLoa @Override protected void loadRepositories(List pathsToLoad) throws IOException, InvalidFormatException, DeserializationException { AasEnvironmentPreconfigurationLoader envLoader = new AasEnvironmentPreconfigurationLoader(rLoader, pathsToLoad); - envLoader.loadPreconfiguredEnvironments(new AasEnvironmentLoader(aasRepository, submodelRepository, conceptDescriptionRepository)); + envLoader.loadPreconfiguredEnvironments(new DefaultAASEnvironment(aasRepository, submodelRepository, conceptDescriptionRepository)); } @Test diff --git a/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/Readme.md b/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/Readme.md index bd1e61310..0e12ecf82 100644 --- a/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/Readme.md +++ b/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/Readme.md @@ -38,8 +38,7 @@ For configuring RBAC rules, all the rbac rules should be configured inside a jso "targetInformation": { "@type": "aas-environment", "aasIds": ["shell001", "shell002"], - "submodelIds": ["7A7104BDAB57E184", "AC69B1CB44F07935"], - "serializationType": "AASX" + "submodelIds": ["7A7104BDAB57E184", "AC69B1CB44F07935"] } }, { @@ -48,8 +47,7 @@ For configuring RBAC rules, all the rbac rules should be configured inside a jso "targetInformation": { "@type": "aas-environment", "aasIds": "*", - "submodelIds": "*", - "serializationType": "ALL" + "submodelIds": "*" } }, { @@ -58,8 +56,7 @@ For configuring RBAC rules, all the rbac rules should be configured inside a jso "targetInformation": { "@type": "aas-environment", "aasIds": ["shell001", "shell002"], - "submodelIds": ["7A7104BDAB57E184"], - "serializationType": "JSON" + "submodelIds": ["7A7104BDAB57E184"] } } ] @@ -67,11 +64,12 @@ For configuring RBAC rules, all the rbac rules should be configured inside a jso The role defines which role is allowed to perform the defined actions. The role is as per the configuration of identity providers or based on the organization. Action could be CREATE, READ, UPDATE, DELETE, and EXECUTE, there could be a single action or multiple actions as a list (cf. admin configuration above). -The targetInformation defines coarse-grained control over the resource, you may define the aasIds and submodelIds with a wildcard (\*), it means the defined role x with action y can perform operations on all the AASs and Submodels. You can also define a specific aasIds and submodelIds in place of the wildcard (\*), then the role x with action y could be performed only on that particular AASs and Submodels. Please note that filtering option is currently not supported so, for serialization requests, if you specify some particular aasIds or submodelIds then serialization request will be denied if there are other AASs or Submodels exists in the environment apart from what configured in the rules. The serializationType could be JSON, AASX, XML, or ALL. +The targetInformation defines coarse-grained control over the resource, you may define the aasIds and submodelIds with a wildcard (\*), it means the defined role x with action y can perform operations on all the AASs and Submodels. You can also define a specific aasIds and submodelIds in place of the wildcard (\*), then the role x with action y could be performed only on that particular AASs and Submodels. Please note that filtering option is currently not supported so, for serialization requests, if you specify some particular aasIds or submodelIds then serialization request will be denied if there are other AASs or Submodels exists in the environment apart from what configured in the rules, and similarly for upload requests. Note: * The Action are fixed as of now and limited to (CREATE, READ, UPDATE, DELETE, and EXECUTE) but later user configurable mapping of these actions would be provided. * For the serialization related requests there should be defined rules for accessing the AASs/Submodels/Concept Descriptions, as the serialization requires all of these elements. For e.g, a role with serialization is configured for Aas Environment target information but if there is no role for reading the AAS/Submodel/Concept Description then the request will be denied. +* For the upload related requests there should be defined rules for reading, creating, and updating the AASs/Submodels/Concept Descriptions, as the upload requests performs creation, updation, and request operations on the AASs/Submodels/Concept Descriptions contained in the uploaded file, hence appropriate rules should be configured for the subject in consideration. ## Action table for RBAC diff --git a/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/AasEnvironmentTargetInformation.java b/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/AasEnvironmentTargetInformation.java index 112686b00..c07122005 100644 --- a/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/AasEnvironmentTargetInformation.java +++ b/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/AasEnvironmentTargetInformation.java @@ -45,13 +45,11 @@ public class AasEnvironmentTargetInformation implements TargetInformation { private List aasIds; private List submodelIds; - private SerializationType serializationType; @JsonCreator - public AasEnvironmentTargetInformation(final @JsonProperty("aasIds") List aasIds, final @JsonProperty("submodelIds") List submodelIds, final @JsonProperty("serializationType") SerializationType serializationType) { + public AasEnvironmentTargetInformation(final @JsonProperty("aasIds") List aasIds, final @JsonProperty("submodelIds") List submodelIds) { this.aasIds = aasIds; this.submodelIds = submodelIds; - this.serializationType = serializationType; } @Override @@ -59,7 +57,6 @@ public Map toMap() { final Map map = new HashMap<>(); map.put("aasIds", aasIds); map.put("submodelIds", submodelIds); - map.put("serializationType", serializationType); return map; } @@ -72,13 +69,9 @@ public List getSubmodelIds() { return submodelIds; } - public SerializationType getSerializationType() { - return serializationType; - } - @Override public int hashCode() { - return Objects.hash(aasIds, serializationType, submodelIds); + return Objects.hash(aasIds, submodelIds); } @Override @@ -90,12 +83,12 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) return false; AasEnvironmentTargetInformation other = (AasEnvironmentTargetInformation) obj; - return Objects.equals(aasIds, other.aasIds) && serializationType == other.serializationType && Objects.equals(submodelIds, other.submodelIds); + return Objects.equals(aasIds, other.aasIds) && Objects.equals(submodelIds, other.submodelIds); } @Override public String toString() { - return "AasEnvironmentTargetInformation [aasIds=" + aasIds + ", submodelIds=" + submodelIds + ", serializationType=" + serializationType + "]"; + return "AasEnvironmentTargetInformation [aasIds=" + aasIds + ", submodelIds=" + submodelIds; } } diff --git a/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/AuthorizedAasEnvironment.java b/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/AuthorizedAasEnvironment.java index 85b5748e9..e1d5c5678 100644 --- a/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/AuthorizedAasEnvironment.java +++ b/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/AuthorizedAasEnvironment.java @@ -27,9 +27,15 @@ import java.io.IOException; import java.util.List; +import java.util.stream.Collectors; import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.SerializationException; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.aas4j.v3.model.Environment; +import org.eclipse.digitaltwin.aas4j.v3.model.Identifiable; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.basyx.aasenvironment.AasEnvironment; +import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.CompleteEnvironment; import org.eclipse.digitaltwin.basyx.authorization.rbac.Action; import org.eclipse.digitaltwin.basyx.authorization.rbac.RbacPermissionResolver; import org.eclipse.digitaltwin.basyx.core.exceptions.InsufficientPermissionException; @@ -52,7 +58,7 @@ public AuthorizedAasEnvironment(AasEnvironment decorated, RbacPermissionResolver @Override public String createJSONAASEnvironmentSerialization(List aasIds, List submodelIds, boolean includeConceptDescriptions) throws SerializationException { - boolean isAuthorized = permissionResolver.hasPermission(Action.READ, new AasEnvironmentTargetInformation(aasIds, submodelIds, SerializationType.JSON)); + boolean isAuthorized = permissionResolver.hasPermission(Action.READ, new AasEnvironmentTargetInformation(aasIds, submodelIds)); throwExceptionIfInsufficientPermission(isAuthorized); @@ -61,7 +67,7 @@ public String createJSONAASEnvironmentSerialization(List aasIds, List aasIds, List submodelIds, boolean includeConceptDescriptions) throws SerializationException { - boolean isAuthorized = permissionResolver.hasPermission(Action.READ, new AasEnvironmentTargetInformation(aasIds, submodelIds, SerializationType.XML)); + boolean isAuthorized = permissionResolver.hasPermission(Action.READ, new AasEnvironmentTargetInformation(aasIds, submodelIds)); throwExceptionIfInsufficientPermission(isAuthorized); @@ -70,7 +76,7 @@ public String createXMLAASEnvironmentSerialization(List aasIds, List aasIds, List submodelIds, boolean includeConceptDescriptions) throws SerializationException, IOException { - boolean isAuthorized = permissionResolver.hasPermission(Action.READ, new AasEnvironmentTargetInformation(aasIds, submodelIds, SerializationType.AASX)); + boolean isAuthorized = permissionResolver.hasPermission(Action.READ, new AasEnvironmentTargetInformation(aasIds, submodelIds)); throwExceptionIfInsufficientPermission(isAuthorized); @@ -82,4 +88,25 @@ private void throwExceptionIfInsufficientPermission(boolean isAuthorized) { throw new InsufficientPermissionException("Insufficient Permission: The current subject does not have the required permissions for this operation."); } + @Override + public void loadEnvironment(CompleteEnvironment completeEnvironment) { + Environment environment = completeEnvironment.getEnvironment(); + + boolean isAuthorized = permissionResolver.hasPermission(Action.CREATE, new AasEnvironmentTargetInformation(getAasIds(environment.getAssetAdministrationShells()), getSubmodelIds(environment.getSubmodels()))); + + throwExceptionIfInsufficientPermission(isAuthorized); + + decorated.loadEnvironment(completeEnvironment); + } + + private List getSubmodelIds(List submodels) { + + return submodels.stream().map(Identifiable::getId).collect(Collectors.toList()); + } + + private List getAasIds(List assetAdministrationShells) { + + return assetAdministrationShells.stream().map(Identifiable::getId).collect(Collectors.toList()); + } + } diff --git a/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/SerializationType.java b/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/SerializationType.java deleted file mode 100644 index b34c4e623..000000000 --- a/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/SerializationType.java +++ /dev/null @@ -1,40 +0,0 @@ -/******************************************************************************* - * Copyright (C) 2024 the Eclipse BaSyx Authors - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * SPDX-License-Identifier: MIT - ******************************************************************************/ - -package org.eclipse.digitaltwin.basyx.aasenvironment.feature.authorization; - -import org.eclipse.digitaltwin.basyx.aasenvironment.AasEnvironment; - -/** - * Enums to represent the types of {@link AasEnvironment} serialization - * - * @author danish - */ -public enum SerializationType { - ALL, - AASX, - JSON, - XML -} diff --git a/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/rbac/AasEnvironmentTargetPermissionVerifier.java b/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/rbac/AasEnvironmentTargetPermissionVerifier.java index 144d6b156..88596c5f6 100644 --- a/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/rbac/AasEnvironmentTargetPermissionVerifier.java +++ b/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/rbac/AasEnvironmentTargetPermissionVerifier.java @@ -28,7 +28,6 @@ import java.util.List; import org.eclipse.digitaltwin.basyx.aasenvironment.feature.authorization.AasEnvironmentTargetInformation; -import org.eclipse.digitaltwin.basyx.aasenvironment.feature.authorization.SerializationType; import org.eclipse.digitaltwin.basyx.authorization.rbac.RbacRule; import org.eclipse.digitaltwin.basyx.authorization.rbac.TargetPermissionVerifier; @@ -45,20 +44,13 @@ public class AasEnvironmentTargetPermissionVerifier implements TargetPermissionV public boolean isVerified(RbacRule rbacRule, AasEnvironmentTargetInformation targetInformation) { List targetInformationShellIds = targetInformation.getAasIds(); List targetInformationSubmdelIds = targetInformation.getSubmodelIds(); - SerializationType targetInformationSerializationType = targetInformation.getSerializationType(); AasEnvironmentTargetInformation rbacRuleAasEnvironmentTargetInformation = (AasEnvironmentTargetInformation) rbacRule.getTargetInformation(); List rbacRuleShellIds = rbacRuleAasEnvironmentTargetInformation.getAasIds(); List rbacRuleSubmdelIds = rbacRuleAasEnvironmentTargetInformation.getSubmodelIds(); - SerializationType rbacRuleSerializationType = rbacRuleAasEnvironmentTargetInformation.getSerializationType(); - return areShellsAllowed(rbacRuleShellIds, targetInformationShellIds) && areSubmodelsAllowed(rbacRuleSubmdelIds, targetInformationSubmdelIds) && isSerializationTypeAllowed(rbacRuleSerializationType, targetInformationSerializationType); - } - - private boolean isSerializationTypeAllowed(SerializationType rbacRuleSerializationType, SerializationType targetInformationSerializationType) { - - return rbacRuleSerializationType.equals(SerializationType.ALL) || rbacRuleSerializationType.equals(targetInformationSerializationType); + return areShellsAllowed(rbacRuleShellIds, targetInformationShellIds) && areSubmodelsAllowed(rbacRuleSubmdelIds, targetInformationSubmdelIds); } private boolean areShellsAllowed(List rbacRuleShellIds, List targetInformationShellIds) { diff --git a/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/DummyAasEnvironmentConfiguration.java b/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/DummyAasEnvironmentConfiguration.java index 74a5fa576..80bb857c2 100644 --- a/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/DummyAasEnvironmentConfiguration.java +++ b/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/DummyAasEnvironmentConfiguration.java @@ -29,12 +29,8 @@ import org.eclipse.digitaltwin.basyx.aasenvironment.AasEnvironment; import org.eclipse.digitaltwin.basyx.aasenvironment.AasEnvironmentFactory; -import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.AasEnvironmentLoader; import org.eclipse.digitaltwin.basyx.aasenvironment.feature.AasEnvironmentFeature; import org.eclipse.digitaltwin.basyx.aasenvironment.feature.DecoratedAasEnvironmentFactory; -import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; -import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository; -import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -54,10 +50,4 @@ public static AasEnvironment getAasEnvironment(AasEnvironmentFactory aasEnvironm return new DecoratedAasEnvironmentFactory(aasEnvironmentFactory, features).create(); } - @Bean - @ConditionalOnMissingBean - public AasEnvironmentLoader createAasEnvironmentLoader(AasRepository aasRepository, SubmodelRepository submodelRepository, ConceptDescriptionRepository conceptDescriptionRepository) { - return new AasEnvironmentLoader(aasRepository, submodelRepository, conceptDescriptionRepository); - } - } diff --git a/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/TestAuthorizedAasEnvironment.java b/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/TestAuthorizedAasEnvironmentSerialization.java similarity index 82% rename from basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/TestAuthorizedAasEnvironment.java rename to basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/TestAuthorizedAasEnvironmentSerialization.java index 37053afec..8ca6519c1 100644 --- a/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/TestAuthorizedAasEnvironment.java +++ b/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/TestAuthorizedAasEnvironmentSerialization.java @@ -48,8 +48,12 @@ import org.eclipse.digitaltwin.basyx.authorization.jwt.JwtTokenDecoder; import org.eclipse.digitaltwin.basyx.authorization.jwt.PublicKeyUtils; import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; import org.eclipse.digitaltwin.basyx.http.serialization.BaSyxHttpTestUtils; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.springframework.boot.SpringApplication; @@ -60,16 +64,21 @@ import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; /** - * Tests for {@link AuthorizedAasEnvironment} feature + * Tests for {@link AuthorizedAasEnvironment} serialization feature * * @author danish */ -public class TestAuthorizedAasEnvironment { +public class TestAuthorizedAasEnvironmentSerialization { + private static final PaginationInfo NO_LIMIT_PAGINATION_INFO = new PaginationInfo(0, null); private static String authenticaltionServerTokenEndpoint = "http://localhost:9096/realms/BaSyx/protocol/openid-connect/token"; private static String clientId = "basyx-client-api"; private static AccessTokenProvider tokenProvider; private static ConfigurableApplicationContext appContext; + private static SubmodelRepository submodelRepo; + private static AasRepository aasRepo; + private static ConceptDescriptionRepository conceptDescriptionRepo; + private static String healthEndpointUrl = "http://127.0.0.1:8081/actuator/health"; @BeforeClass @@ -77,8 +86,41 @@ public static void setUp() throws FileNotFoundException, IOException { tokenProvider = new AccessTokenProvider(authenticaltionServerTokenEndpoint, clientId); appContext = new SpringApplication(DummyAasEnvironmentComponent.class).run(new String[] {}); - - addDummyElementsToRepositories(); + + submodelRepo = appContext.getBean(SubmodelRepository.class); + aasRepo = appContext.getBean(AasRepository.class); + conceptDescriptionRepo = appContext.getBean(ConceptDescriptionRepository.class); + } + + @AfterClass + public static void tearDown() { + appContext.close(); + } + + @Before + public void initializeRepositories() throws FileNotFoundException, IOException { + configureSecurityContext(); + + createDummyShellsOnRepository(TestAASEnvironmentSerialization.createDummyShells(), aasRepo); + createDummySubmodelsOnRepository(TestAASEnvironmentSerialization.createDummySubmodels(), submodelRepo); + createDummyConceptDescriptionsOnRepository(TestAASEnvironmentSerialization.createDummyConceptDescriptions(), conceptDescriptionRepo); + + clearSecurityContext(); + } + + @After + public void reset() throws FileNotFoundException, IOException { + configureSecurityContext(); + + Collection assetAdministrationShells = aasRepo.getAllAas(NO_LIMIT_PAGINATION_INFO).getResult(); + Collection submodels = submodelRepo.getAllSubmodels(NO_LIMIT_PAGINATION_INFO).getResult(); + Collection conceptDescriptions = conceptDescriptionRepo.getAllConceptDescriptions(NO_LIMIT_PAGINATION_INFO).getResult(); + + assetAdministrationShells.stream().forEach(aas -> aasRepo.deleteAas(aas.getId())); + submodels.stream().forEach(sm -> submodelRepo.deleteSubmodel(sm.getId())); + conceptDescriptions.stream().forEach(cd -> conceptDescriptionRepo.deleteConceptDescription(cd.getId())); + + clearSecurityContext(); } @Test @@ -92,7 +134,7 @@ public void healthEndpointWithoutAuthorization() throws IOException, ParseExcept } @Test - public void createSerializationWithCorrectRoleAndPermission() throws IOException { + public void createSerializationWithCorrectRoleAndPermission() throws IOException { DummyCredential dummyCredential = DummyCredentialStore.BASYX_READER_CREDENTIAL; boolean includeConceptDescription = true; @@ -103,30 +145,6 @@ public void createSerializationWithCorrectRoleAndPermission() throws IOException assertEquals(HttpStatus.OK.value(), retrievalResponse.getCode()); } - @Test - public void createSerializationWithCorrectRoleAndSpecificSerializationPermission() throws IOException { - DummyCredential dummyCredential = DummyCredentialStore.BASYX_READER_TWO_CREDENTIAL; - - boolean includeConceptDescription = true; - - String accessToken = tokenProvider.getAccessToken(dummyCredential.getUsername(), dummyCredential.getPassword()); - - CloseableHttpResponse retrievalResponse = getElementWithAuthorization(TestAasEnvironmentHTTP.createSerializationURL(includeConceptDescription), accessToken, new BasicHeader("Accept", TestAasEnvironmentHTTP.AASX_MIMETYPE)); - assertEquals(HttpStatus.OK.value(), retrievalResponse.getCode()); - } - - @Test - public void createSerializationWithCorrectRoleAndUnauthorizedSpecificSerializationPermission() throws IOException { - DummyCredential dummyCredential = DummyCredentialStore.BASYX_READER_TWO_CREDENTIAL; - - boolean includeConceptDescription = true; - - String accessToken = tokenProvider.getAccessToken(dummyCredential.getUsername(), dummyCredential.getPassword()); - - CloseableHttpResponse retrievalResponse = getElementWithAuthorization(TestAasEnvironmentHTTP.createSerializationURL(includeConceptDescription), accessToken, new BasicHeader("Accept", TestAasEnvironmentHTTP.JSON_MIMETYPE)); - assertEquals(HttpStatus.FORBIDDEN.value(), retrievalResponse.getCode()); - } - @Test public void createSerializationWithInsufficientPermissionRole() throws IOException { DummyCredential dummyCredential = DummyCredentialStore.BASYX_UPDATER_CREDENTIAL; @@ -171,7 +189,11 @@ public void createSerializationWithCorrectRoleAndInsufficientTargetPermission() assertEquals(HttpStatus.FORBIDDEN.value(), retrievalResponse.getCode()); } - private static void addDummyElementsToRepositories() throws FileNotFoundException, IOException { + private void clearSecurityContext() { + SecurityContextHolder.clearContext(); + } + + private void configureSecurityContext() throws FileNotFoundException, IOException { String adminToken = getAdminAccessToken(); String modulus = getStringFromFile("authorization/modulus.txt"); @@ -182,12 +204,6 @@ private static void addDummyElementsToRepositories() throws FileNotFoundExceptio Jwt jwt = JwtTokenDecoder.decodeJwt(adminToken, rsaPublicKey); SecurityContextHolder.getContext().setAuthentication(new JwtAuthenticationToken(jwt)); - - createDummyShellsOnRepository(TestAASEnvironmentSerialization.createDummyShells(), appContext.getBean(AasRepository.class)); - createDummySubmodelsOnRepository(TestAASEnvironmentSerialization.createDummySubmodels(), appContext.getBean(SubmodelRepository.class)); - createDummyConceptDescriptionsOnRepository(TestAASEnvironmentSerialization.createDummyConceptDescriptions(), appContext.getBean(ConceptDescriptionRepository.class)); - - SecurityContextHolder.clearContext(); } protected CloseableHttpResponse getElementWithAuthorization(String url, String accessToken, Header header) throws IOException { diff --git a/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/TestAuthorizedAasEnvironmentUpload.java b/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/TestAuthorizedAasEnvironmentUpload.java new file mode 100644 index 000000000..4069daa3a --- /dev/null +++ b/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasenvironment/feature/authorization/TestAuthorizedAasEnvironmentUpload.java @@ -0,0 +1,209 @@ +/******************************************************************************* + * Copyright (C) 2024 the Eclipse BaSyx Authors + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * SPDX-License-Identifier: MIT + ******************************************************************************/ + +package org.eclipse.digitaltwin.basyx.aasenvironment.feature.authorization; + +import static org.junit.Assert.assertEquals; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.security.interfaces.RSAPublicKey; +import java.util.Collection; + +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.ParseException; +import org.eclipse.basyx.digitaltwin.aasenvironment.http.TestAasEnvironmentHTTP; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; +import org.eclipse.digitaltwin.basyx.authorization.AccessTokenProvider; +import org.eclipse.digitaltwin.basyx.authorization.DummyCredential; +import org.eclipse.digitaltwin.basyx.authorization.DummyCredentialStore; +import org.eclipse.digitaltwin.basyx.authorization.jwt.JwtTokenDecoder; +import org.eclipse.digitaltwin.basyx.authorization.jwt.PublicKeyUtils; +import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.http.serialization.BaSyxHttpTestUtils; +import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.util.ResourceUtils; + +/** + * Tests for {@link AuthorizedAasEnvironment} upload feature + * + * @author danish + */ +public class TestAuthorizedAasEnvironmentUpload { + + private static final PaginationInfo NO_LIMIT_PAGINATION_INFO = new PaginationInfo(0, null); + private static String authenticaltionServerTokenEndpoint = "http://localhost:9096/realms/BaSyx/protocol/openid-connect/token"; + private static String clientId = "basyx-client-api"; + private static AccessTokenProvider tokenProvider; + private static ConfigurableApplicationContext appContext; + private static SubmodelRepository submodelRepo; + private static AasRepository aasRepo; + private static ConceptDescriptionRepository conceptDescriptionRepo; + + private static String healthEndpointUrl = "http://127.0.0.1:8081/actuator/health"; + + @Before + public void setUp() throws FileNotFoundException, IOException { + tokenProvider = new AccessTokenProvider(authenticaltionServerTokenEndpoint, clientId); + + appContext = new SpringApplication(DummyAasEnvironmentComponent.class).run(new String[] {}); + + submodelRepo = appContext.getBean(SubmodelRepository.class); + aasRepo = appContext.getBean(AasRepository.class); + conceptDescriptionRepo = appContext.getBean(ConceptDescriptionRepository.class); + } + + @After + public void reset() throws FileNotFoundException, IOException { + + configureSecurityContext(); + + Collection assetAdministrationShells = aasRepo.getAllAas(NO_LIMIT_PAGINATION_INFO).getResult(); + Collection submodels = submodelRepo.getAllSubmodels(NO_LIMIT_PAGINATION_INFO).getResult(); + Collection conceptDescriptions = conceptDescriptionRepo.getAllConceptDescriptions(NO_LIMIT_PAGINATION_INFO).getResult(); + + assetAdministrationShells.stream().forEach(aas -> aasRepo.deleteAas(aas.getId())); + submodels.stream().forEach(sm -> submodelRepo.deleteSubmodel(sm.getId())); + conceptDescriptions.stream().forEach(cd -> conceptDescriptionRepo.deleteConceptDescription(cd.getId())); + + clearSecurityContext(); + + appContext.close(); + } + + @Test + public void healthEndpointWithoutAuthorization() throws IOException, ParseException { + String expectedHealthEndpointOutput = getStringFromFile("authorization/HealthOutput.json"); + + CloseableHttpResponse healthCheckResponse = BaSyxHttpTestUtils.executeGetOnURL(healthEndpointUrl); + assertEquals(HttpStatus.OK.value(), healthCheckResponse.getCode()); + + BaSyxHttpTestUtils.assertSameJSONContent(expectedHealthEndpointOutput, BaSyxHttpTestUtils.getResponseAsString(healthCheckResponse)); + } + + @Test + public void uploadWithCorrectRoleAndPermission() throws IOException { + DummyCredential dummyCredential = DummyCredentialStore.BASYX_UPLOADER; + + String accessToken = tokenProvider.getAccessToken(dummyCredential.getUsername(), dummyCredential.getPassword()); + + CloseableHttpResponse retrievalResponse = BaSyxHttpTestUtils.executePostRequest(HttpClients.createDefault(), createAuthorizedPostRequestWithFile(TestAasEnvironmentHTTP.AASX_ENV_PATH, TestAasEnvironmentHTTP.AASX_MIMETYPE, accessToken)); + assertEquals(HttpStatus.OK.value(), retrievalResponse.getCode()); + } + + @Test + public void uploadWithCorrectRoleAndSpecificAasPermission() throws IOException { + DummyCredential dummyCredential = DummyCredentialStore.BASYX_UPLOADER_TWO; + + String accessToken = tokenProvider.getAccessToken(dummyCredential.getUsername(), dummyCredential.getPassword()); + + CloseableHttpResponse retrievalResponse = BaSyxHttpTestUtils.executePostRequest(HttpClients.createDefault(), createAuthorizedPostRequestWithFile(TestAasEnvironmentHTTP.AASX_ENV_PATH, TestAasEnvironmentHTTP.AASX_MIMETYPE, accessToken)); + assertEquals(HttpStatus.OK.value(), retrievalResponse.getCode()); + } + + @Test + public void uploadWithCorrectRoleAndUnauthorizedSpecificAasPermission() throws IOException { + DummyCredential dummyCredential = DummyCredentialStore.BASYX_UPLOADER_THREE; + + String accessToken = tokenProvider.getAccessToken(dummyCredential.getUsername(), dummyCredential.getPassword()); + + CloseableHttpResponse retrievalResponse = BaSyxHttpTestUtils.executePostRequest(HttpClients.createDefault(), createAuthorizedPostRequestWithFile(TestAasEnvironmentHTTP.AASX_ENV_PATH, TestAasEnvironmentHTTP.AASX_MIMETYPE, accessToken)); + assertEquals(HttpStatus.FORBIDDEN.value(), retrievalResponse.getCode()); + } + + @Test + public void uploadWithNoAuthorization() throws IOException { + CloseableHttpResponse retrievalResponse = BaSyxHttpTestUtils.executePostRequest(HttpClients.createDefault(), createPostRequestWithFile(TestAasEnvironmentHTTP.AASX_ENV_PATH, TestAasEnvironmentHTTP.AASX_MIMETYPE)); + assertEquals(HttpStatus.UNAUTHORIZED.value(), retrievalResponse.getCode()); + } + + protected CloseableHttpResponse getElementWithAuthorization(String url, String accessToken, Header header) throws IOException { + return BaSyxHttpTestUtils.executeAuthorizedGetOnURL(url, accessToken, header); + } + + protected CloseableHttpResponse getElementWithNoAuthorization(String url, Header header) throws IOException { + return BaSyxHttpTestUtils.executeGetOnURL(url, header); + } + + private void clearSecurityContext() { + SecurityContextHolder.clearContext(); + } + + private void configureSecurityContext() throws FileNotFoundException, IOException { + String adminToken = getAdminAccessToken(); + + String modulus = getStringFromFile("authorization/modulus.txt"); + String exponent = "AQAB"; + + RSAPublicKey rsaPublicKey = PublicKeyUtils.buildPublicKey(modulus, exponent); + + Jwt jwt = JwtTokenDecoder.decodeJwt(adminToken, rsaPublicKey); + + SecurityContextHolder.getContext().setAuthentication(new JwtAuthenticationToken(jwt)); + } + + private static HttpPost createAuthorizedPostRequestWithFile(String filepath, String contentType, String accessToken) throws FileNotFoundException { + java.io.File file = ResourceUtils.getFile("classpath:" + filepath); + + return BaSyxHttpTestUtils.createAuthorizedPostRequestWithFile(getAASXUploadURL(), file, contentType, accessToken); + } + + private static HttpPost createPostRequestWithFile(String filepath, String contentType) throws FileNotFoundException { + java.io.File file = ResourceUtils.getFile("classpath:" + filepath); + + return BaSyxHttpTestUtils.createPostRequestWithFile(getAASXUploadURL(), file, contentType); + } + + private static String getAASXUploadURL() { + return TestAasEnvironmentHTTP.getURL() + "/upload"; + } + + private static String getAdminAccessToken() { + DummyCredential dummyCredential = DummyCredentialStore.ADMIN_CREDENTIAL; + + return tokenProvider.getAccessToken(dummyCredential.getUsername(), dummyCredential.getPassword()); + } + + private static String getStringFromFile(String fileName) throws FileNotFoundException, IOException { + return BaSyxHttpTestUtils.readJSONStringFromClasspath(fileName); + } + +} diff --git a/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/test/resources/rbac_rules.json b/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/test/resources/rbac_rules.json index 9c665a0ab..8a0a6b205 100644 --- a/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/test/resources/rbac_rules.json +++ b/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/test/resources/rbac_rules.json @@ -264,6 +264,15 @@ "conceptDescriptionId": "*" } }, + { + "role": "basyx-creator", + "action": "CREATE", + "targetInformation": { + "@type": "aas-environment", + "aasIds": "*", + "submodelIds": "*" + } + }, { "role": "basyx-updater", "action": "UPDATE", @@ -302,8 +311,7 @@ "targetInformation": { "@type": "aas-environment", "aasIds": "*", - "submodelIds": "*", - "serializationType": "ALL" + "submodelIds": "*" } }, { @@ -312,8 +320,7 @@ "targetInformation": { "@type": "aas-environment", "aasIds": "*", - "submodelIds": "*", - "serializationType": "ALL" + "submodelIds": "*" } }, { @@ -322,8 +329,7 @@ "targetInformation": { "@type": "aas-environment", "aasIds": "*", - "submodelIds": "*", - "serializationType": "AASX" + "submodelIds": "*" } }, { @@ -357,8 +363,7 @@ "targetInformation": { "@type": "aas-environment", "aasIds": "*", - "submodelIds": "*", - "serializationType": "XML" + "submodelIds": "*" } }, { @@ -367,8 +372,7 @@ "targetInformation": { "@type": "aas-environment", "aasIds": ["shell001", "shell002"], - "submodelIds": ["7A7104BDAB57E184", "AC69B1CB44F07935"], - "serializationType": "AASX" + "submodelIds": ["7A7104BDAB57E184", "AC69B1CB44F07935"] } }, { @@ -402,8 +406,235 @@ "targetInformation": { "@type": "aas-environment", "aasIds": ["shell001", "shell002"], - "submodelIds": ["7A7104BDAB57E184"], - "serializationType": "JSON" + "submodelIds": ["7A7104BDAB57E184"] + } + }, + { + "role": "basyx-uploader", + "action": "CREATE", + "targetInformation": { + "@type": "aas", + "aasId": "*" + } + }, + { + "role": "basyx-uploader", + "action": "READ", + "targetInformation": { + "@type": "aas", + "aasId": "*" + } + }, + { + "role": "basyx-uploader", + "action": "READ", + "targetInformation": { + "@type": "submodel", + "submodelId": "*", + "submodelElementIdShortPath": "*" + } + }, + { + "role": "basyx-uploader", + "action": "READ", + "targetInformation": { + "@type": "concept-description", + "conceptDescriptionId": "*" + } + }, + { + "role": "basyx-uploader", + "action": "UPDATE", + "targetInformation": { + "@type": "concept-description", + "conceptDescriptionId": "*" + } + }, + { + "role": "basyx-uploader", + "action": "CREATE", + "targetInformation": { + "@type": "submodel", + "submodelId": "*", + "submodelElementIdShortPath": "*" + } + }, + { + "role": "basyx-uploader", + "action": "UPDATE", + "targetInformation": { + "@type": "submodel", + "submodelId": "*", + "submodelElementIdShortPath": "*" + } + }, + { + "role": "basyx-uploader", + "action": "CREATE", + "targetInformation": { + "@type": "concept-description", + "conceptDescriptionId": "*" + } + }, + { + "role": "basyx-uploader", + "action": "CREATE", + "targetInformation": { + "@type": "aas-environment", + "aasIds": "*", + "submodelIds": "*" + } + }, + { + "role": "basyx-uploader-two", + "action": "CREATE", + "targetInformation": { + "@type": "aas", + "aasId": "http://customer.com/aas/9175_7013_7091_9168" + } + }, + { + "role": "basyx-uploader-two", + "action": "READ", + "targetInformation": { + "@type": "aas", + "aasId": "http://customer.com/aas/9175_7013_7091_9168" + } + }, + { + "role": "basyx-uploader-two", + "action": "READ", + "targetInformation": { + "@type": "submodel", + "submodelId": "*", + "submodelElementIdShortPath": "*" + } + }, + { + "role": "basyx-uploader-two", + "action": "READ", + "targetInformation": { + "@type": "concept-description", + "conceptDescriptionId": "*" + } + }, + { + "role": "basyx-uploader-two", + "action": "UPDATE", + "targetInformation": { + "@type": "concept-description", + "conceptDescriptionId": "*" + } + }, + { + "role": "basyx-uploader-two", + "action": "CREATE", + "targetInformation": { + "@type": "submodel", + "submodelId": "*", + "submodelElementIdShortPath": "*" + } + }, + { + "role": "basyx-uploader-two", + "action": "UPDATE", + "targetInformation": { + "@type": "submodel", + "submodelId": "*", + "submodelElementIdShortPath": "*" + } + }, + { + "role": "basyx-uploader-two", + "action": "CREATE", + "targetInformation": { + "@type": "concept-description", + "conceptDescriptionId": "*" + } + }, + { + "role": "basyx-uploader-two", + "action": "CREATE", + "targetInformation": { + "@type": "aas-environment", + "aasIds": "http://customer.com/aas/9175_7013_7091_9168", + "submodelIds": ["http://i40.customer.com/type/1/1/7A7104BDAB57E184", "http://i40.customer.com/type/1/1/1A7B62B529F19152", "http://i40.customer.com/instance/1/1/AC69B1CB44F07935"] + } + }, + { + "role": "basyx-uploader-three", + "action": "CREATE", + "targetInformation": { + "@type": "aas", + "aasId": "http://customer.com/aas/9175_7013_7091_9168" + } + }, + { + "role": "basyx-uploader-three", + "action": "READ", + "targetInformation": { + "@type": "aas", + "aasId": "http://customer.com/aas/9175_7013_7091_9168" + } + }, + { + "role": "basyx-uploader-three", + "action": "READ", + "targetInformation": { + "@type": "submodel", + "submodelId": "*", + "submodelElementIdShortPath": "*" + } + }, + { + "role": "basyx-uploader-three", + "action": "READ", + "targetInformation": { + "@type": "concept-description", + "conceptDescriptionId": "*" + } + }, + { + "role": "basyx-uploader-three", + "action": "UPDATE", + "targetInformation": { + "@type": "concept-description", + "conceptDescriptionId": "*" + } + }, + { + "role": "basyx-uploader-three", + "action": "CREATE", + "targetInformation": { + "@type": "submodel", + "submodelId": "*", + "submodelElementIdShortPath": "*" + } + }, + { + "role": "basyx-uploader-three", + "action": "UPDATE", + "targetInformation": { + "@type": "submodel", + "submodelId": "*", + "submodelElementIdShortPath": "*" + } + }, + { + "role": "basyx-uploader-three", + "action": "CREATE", + "targetInformation": { + "@type": "concept-description", + "conceptDescriptionId": "*" + } + }, + { + "role": "basyx-uploader-three", + "action": "CREATE", + "targetInformation": { + "@type": "aas-environment", + "aasIds": "AuthorizedAasID", + "submodelIds": ["http://i40.customer.com/type/1/1/7A7104BDAB57E184", "http://i40.customer.com/type/1/1/1A7B62B529F19152", "http://i40.customer.com/instance/1/1/AC69B1CB44F07935"] } } ] \ No newline at end of file diff --git a/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/test/resources/testEnvironment.aasx b/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/test/resources/testEnvironment.aasx new file mode 100644 index 000000000..9cd1b82d6 Binary files /dev/null and b/basyx.aasenvironment/basyx.aasenvironment-feature-authorization/src/test/resources/testEnvironment.aasx differ diff --git a/basyx.aasenvironment/basyx.aasenvironment-http/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/http/AasEnvironmentApiHTTPController.java b/basyx.aasenvironment/basyx.aasenvironment-http/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/http/AasEnvironmentApiHTTPController.java index a46f23205..cc227b308 100644 --- a/basyx.aasenvironment/basyx.aasenvironment-http/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/http/AasEnvironmentApiHTTPController.java +++ b/basyx.aasenvironment/basyx.aasenvironment-http/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/http/AasEnvironmentApiHTTPController.java @@ -33,7 +33,6 @@ import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.DeserializationException; import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.SerializationException; import org.eclipse.digitaltwin.basyx.aasenvironment.AasEnvironment; -import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.AasEnvironmentLoader; import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.CompleteEnvironment; import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.CompleteEnvironment.EnvironmentType; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; @@ -65,13 +64,10 @@ public class AasEnvironmentApiHTTPController implements AASEnvironmentHTTPApi { private final AasEnvironment aasEnvironment; - private final AasEnvironmentLoader aasEnvironmentLoader; - @Autowired - public AasEnvironmentApiHTTPController(HttpServletRequest request, AasEnvironment aasEnvironment, AasEnvironmentLoader aasEnvironmentLoader) { + public AasEnvironmentApiHTTPController(HttpServletRequest request, AasEnvironment aasEnvironment) { this.request = request; this.aasEnvironment = aasEnvironment; - this.aasEnvironmentLoader = aasEnvironmentLoader; } @Override @@ -113,7 +109,7 @@ public ResponseEntity uploadEnvironment(MultipartFile envFile) { if (envType == null) envType = EnvironmentType.AASX; - aasEnvironmentLoader.loadEnvironment(CompleteEnvironment.fromInputStream(envFile.getInputStream(), envType)); + aasEnvironment.loadEnvironment(CompleteEnvironment.fromInputStream(envFile.getInputStream(), envType)); } catch (InvalidFormatException e) { return new ResponseEntity(false, HttpStatus.BAD_REQUEST); diff --git a/basyx.aasenvironment/basyx.aasenvironment-http/src/test/java/org/eclipse/basyx/digitaltwin/aasenvironment/http/DummyAASEnvironmentComponent.java b/basyx.aasenvironment/basyx.aasenvironment-http/src/test/java/org/eclipse/basyx/digitaltwin/aasenvironment/http/DummyAASEnvironmentComponent.java index d01333dbc..2ced71b57 100644 --- a/basyx.aasenvironment/basyx.aasenvironment-http/src/test/java/org/eclipse/basyx/digitaltwin/aasenvironment/http/DummyAASEnvironmentComponent.java +++ b/basyx.aasenvironment/basyx.aasenvironment-http/src/test/java/org/eclipse/basyx/digitaltwin/aasenvironment/http/DummyAASEnvironmentComponent.java @@ -37,7 +37,6 @@ import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultConceptDescription; import org.eclipse.digitaltwin.basyx.aasenvironment.AasEnvironment; import org.eclipse.digitaltwin.basyx.aasenvironment.base.DefaultAASEnvironment; -import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.AasEnvironmentLoader; import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; @@ -60,11 +59,6 @@ public AasEnvironment createAasEnvironmentSerialization(AasRepository aasReposit return new DefaultAASEnvironment(aasRepository, submodelRepository, conceptDescriptionRepository); } - @Bean - public AasEnvironmentLoader createAasEnvironmentLoader(AasRepository aasRepository, SubmodelRepository submodelRepository, ConceptDescriptionRepository conceptDescriptionRepository) { - return new AasEnvironmentLoader(aasRepository, submodelRepository, conceptDescriptionRepository); - } - public void initRepositories(AasRepository aasRepository, SubmodelRepository submodelRepository, ConceptDescriptionRepository conceptDescriptionRepository) { createDummySubmodels().forEach(submodelRepository::createSubmodel); createDummyShells().forEach(aasRepository::createAas); diff --git a/basyx.aasenvironment/basyx.aasenvironment-http/src/test/java/org/eclipse/basyx/digitaltwin/aasenvironment/http/TestAasEnvironmentHTTP.java b/basyx.aasenvironment/basyx.aasenvironment-http/src/test/java/org/eclipse/basyx/digitaltwin/aasenvironment/http/TestAasEnvironmentHTTP.java index d8dd36c89..51e64b3ff 100644 --- a/basyx.aasenvironment/basyx.aasenvironment-http/src/test/java/org/eclipse/basyx/digitaltwin/aasenvironment/http/TestAasEnvironmentHTTP.java +++ b/basyx.aasenvironment/basyx.aasenvironment-http/src/test/java/org/eclipse/basyx/digitaltwin/aasenvironment/http/TestAasEnvironmentHTTP.java @@ -69,7 +69,7 @@ public class TestAasEnvironmentHTTP { public static final String XML_MIMETYPE = "application/xml"; public static final String AASX_MIMETYPE = "application/asset-administration-shell-package+xml"; - private static final String AASX_ENV_PATH = "testEnvironment.aasx"; + public static final String AASX_ENV_PATH = "testEnvironment.aasx"; private static final String JSON_ENV_PATH = "testEnvironment.json"; private static final String XML_ENV_PATH = "testEnvironment.xml"; private static final String WRONGEXT_ENV_PATH = "testEnvironment.txt"; diff --git a/basyx.aasenvironment/basyx.aasenvironment.component/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/component/AasEnvironmentConfiguration.java b/basyx.aasenvironment/basyx.aasenvironment.component/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/component/AasEnvironmentConfiguration.java index e2f2fd024..547adea41 100644 --- a/basyx.aasenvironment/basyx.aasenvironment.component/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/component/AasEnvironmentConfiguration.java +++ b/basyx.aasenvironment/basyx.aasenvironment.component/src/main/java/org/eclipse/digitaltwin/basyx/aasenvironment/component/AasEnvironmentConfiguration.java @@ -29,12 +29,8 @@ import org.eclipse.digitaltwin.basyx.aasenvironment.AasEnvironment; import org.eclipse.digitaltwin.basyx.aasenvironment.AasEnvironmentFactory; -import org.eclipse.digitaltwin.basyx.aasenvironment.environmentloader.AasEnvironmentLoader; import org.eclipse.digitaltwin.basyx.aasenvironment.feature.AasEnvironmentFeature; import org.eclipse.digitaltwin.basyx.aasenvironment.feature.DecoratedAasEnvironmentFactory; -import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; -import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository; -import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -54,8 +50,4 @@ public static AasEnvironment getAasEnvironment(AasEnvironmentFactory aasEnvironm return new DecoratedAasEnvironmentFactory(aasEnvironmentFactory, features).create(); } - @Bean - public AasEnvironmentLoader createAasEnvironmentLoader(AasRepository aasRepository, SubmodelRepository submodelRepository, ConceptDescriptionRepository conceptDescriptionRepository) { - return new AasEnvironmentLoader(aasRepository, submodelRepository, conceptDescriptionRepository); - } } diff --git a/basyx.aasenvironment/basyx.aasenvironment.component/src/main/resources/rbac_rules.json b/basyx.aasenvironment/basyx.aasenvironment.component/src/main/resources/rbac_rules.json index 9c665a0ab..8ea3e6ab8 100644 --- a/basyx.aasenvironment/basyx.aasenvironment.component/src/main/resources/rbac_rules.json +++ b/basyx.aasenvironment/basyx.aasenvironment.component/src/main/resources/rbac_rules.json @@ -302,8 +302,7 @@ "targetInformation": { "@type": "aas-environment", "aasIds": "*", - "submodelIds": "*", - "serializationType": "ALL" + "submodelIds": "*" } }, { @@ -312,8 +311,7 @@ "targetInformation": { "@type": "aas-environment", "aasIds": "*", - "submodelIds": "*", - "serializationType": "ALL" + "submodelIds": "*" } }, { @@ -322,8 +320,7 @@ "targetInformation": { "@type": "aas-environment", "aasIds": "*", - "submodelIds": "*", - "serializationType": "AASX" + "submodelIds": "*" } }, { @@ -357,8 +354,7 @@ "targetInformation": { "@type": "aas-environment", "aasIds": "*", - "submodelIds": "*", - "serializationType": "XML" + "submodelIds": "*" } }, { @@ -367,8 +363,7 @@ "targetInformation": { "@type": "aas-environment", "aasIds": ["shell001", "shell002"], - "submodelIds": ["7A7104BDAB57E184", "AC69B1CB44F07935"], - "serializationType": "AASX" + "submodelIds": ["7A7104BDAB57E184", "AC69B1CB44F07935"] } }, { @@ -402,8 +397,7 @@ "targetInformation": { "@type": "aas-environment", "aasIds": ["shell001", "shell002"], - "submodelIds": ["7A7104BDAB57E184"], - "serializationType": "JSON" + "submodelIds": ["7A7104BDAB57E184"] } } ] \ No newline at end of file diff --git a/basyx.common/basyx.authorization/src/test/java/org/eclipse/digitaltwin/basyx/authorization/DummyCredentialStore.java b/basyx.common/basyx.authorization/src/test/java/org/eclipse/digitaltwin/basyx/authorization/DummyCredentialStore.java index 539906632..fd58971f4 100644 --- a/basyx.common/basyx.authorization/src/test/java/org/eclipse/digitaltwin/basyx/authorization/DummyCredentialStore.java +++ b/basyx.common/basyx.authorization/src/test/java/org/eclipse/digitaltwin/basyx/authorization/DummyCredentialStore.java @@ -58,4 +58,8 @@ public class DummyCredentialStore { public static final DummyCredential BASYX_READER_SERIALIZATION_CREDENTIAL = new DummyCredential("basyx.reader.serialization", "basyxreaderserialization"); public static final DummyCredential BASYX_READER_SERIALIZATION_CREDENTIAL_TWO = new DummyCredential("basyx.reader.serialization.2", "basyxreaderserialization2"); + + public static final DummyCredential BASYX_UPLOADER = new DummyCredential("basyx.uploader", "basyxuploader"); + public static final DummyCredential BASYX_UPLOADER_TWO = new DummyCredential("basyx.uploader.2", "basyxuploader2"); + public static final DummyCredential BASYX_UPLOADER_THREE = new DummyCredential("basyx.uploader.3", "basyxuploader3"); } \ No newline at end of file diff --git a/basyx.common/basyx.http/src/test/java/org/eclipse/digitaltwin/basyx/http/serialization/BaSyxHttpTestUtils.java b/basyx.common/basyx.http/src/test/java/org/eclipse/digitaltwin/basyx/http/serialization/BaSyxHttpTestUtils.java index e65c4f78a..0e84e0b3f 100644 --- a/basyx.common/basyx.http/src/test/java/org/eclipse/digitaltwin/basyx/http/serialization/BaSyxHttpTestUtils.java +++ b/basyx.common/basyx.http/src/test/java/org/eclipse/digitaltwin/basyx/http/serialization/BaSyxHttpTestUtils.java @@ -390,6 +390,20 @@ public static HttpPost createPostRequestWithFile(String url, java.io.File file, return postRequest; } + public static HttpPost createAuthorizedPostRequestWithFile(String url, java.io.File file, String contentType, String accessToken) { + HttpPost postRequest = new HttpPost(url); + postRequest.setHeader("Authorization", "Bearer " + accessToken); + + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + + builder.addPart("file", new FileBody(file, ContentType.create(contentType))); + builder.setContentType(ContentType.MULTIPART_FORM_DATA); + + HttpEntity multipart = builder.build(); + postRequest.setEntity(multipart); + return postRequest; + } + private static HttpPost createAuthorizedPostRequest(String url, String content, String accessToken) { HttpPost aasCreateRequest = createPostRequestWithAuthorizationHeader(url, accessToken); diff --git a/examples/keycloak/BaSyx-realm.json b/examples/keycloak/BaSyx-realm.json index 53b407378..f3fe89e9e 100644 --- a/examples/keycloak/BaSyx-realm.json +++ b/examples/keycloak/BaSyx-realm.json @@ -68,6 +68,14 @@ "clientRole" : false, "containerId" : "bcb69552-bf11-4249-a3eb-d0c3ab54a570", "attributes" : { } + }, { + "id" : "14dd6864-bcbd-46c3-b9b6-269ce036badc", + "name" : "basyx-uploader-three", + "description" : "", + "composite" : false, + "clientRole" : false, + "containerId" : "bcb69552-bf11-4249-a3eb-d0c3ab54a570", + "attributes" : { } }, { "id" : "502bc902-9de6-4552-98b0-55187b847272", "name" : "user", @@ -196,6 +204,14 @@ "clientRole" : false, "containerId" : "bcb69552-bf11-4249-a3eb-d0c3ab54a570", "attributes" : { } + }, { + "id" : "7b698a18-f272-4178-a6a2-d09e714c488e", + "name" : "basyx-uploader-two", + "description" : "", + "composite" : false, + "clientRole" : false, + "containerId" : "bcb69552-bf11-4249-a3eb-d0c3ab54a570", + "attributes" : { } }, { "id" : "09fa63ab-86ae-40bb-9497-56ee46070200", "name" : "basyx-sme-reader", @@ -204,6 +220,14 @@ "clientRole" : false, "containerId" : "bcb69552-bf11-4249-a3eb-d0c3ab54a570", "attributes" : { } + }, { + "id" : "7065a5d2-3ab5-471a-be8c-cda64b6ce319", + "name" : "basyx-uploader", + "description" : "", + "composite" : false, + "clientRole" : false, + "containerId" : "bcb69552-bf11-4249-a3eb-d0c3ab54a570", + "attributes" : { } }, { "id" : "012af7ea-5eb7-4156-929a-acbae548e105", "name" : "basyx-deleter", @@ -577,7 +601,7 @@ "otpPolicyLookAheadWindow" : 1, "otpPolicyPeriod" : 30, "otpPolicyCodeReusable" : false, - "otpSupportedApplications" : [ "totpAppGoogleName", "totpAppMicrosoftAuthenticatorName", "totpAppFreeOTPName" ], + "otpSupportedApplications" : [ "totpAppMicrosoftAuthenticatorName", "totpAppGoogleName", "totpAppFreeOTPName" ], "webAuthnPolicyRpEntityName" : "keycloak", "webAuthnPolicySignatureAlgorithms" : [ "ES256" ], "webAuthnPolicyRpId" : "", @@ -1041,6 +1065,72 @@ "realmRoles" : [ "basyx-updater-two", "default-roles-basyx" ], "notBefore" : 0, "groups" : [ ] + }, { + "id" : "cb7df854-827d-4d34-a01f-33cdf07f5cea", + "createdTimestamp" : 1708702290219, + "username" : "basyx.uploader", + "enabled" : true, + "totp" : false, + "emailVerified" : false, + "firstName" : "", + "lastName" : "", + "credentials" : [ { + "id" : "7df6170e-1631-434d-8380-62f750c563cf", + "type" : "password", + "userLabel" : "My password", + "createdDate" : 1708702304670, + "secretData" : "{\"value\":\"M7YGdklaJzphwjjWGLfb990lR4NY4rbLTQ1LAPptEuc=\",\"salt\":\"MEYVvEIkg54+jjXQS47dbA==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-basyx", "basyx-uploader" ], + "notBefore" : 0, + "groups" : [ ] + }, { + "id" : "3c94e1e0-caac-48c3-a31c-c9f555233a46", + "createdTimestamp" : 1708932762171, + "username" : "basyx.uploader.2", + "enabled" : true, + "totp" : false, + "emailVerified" : false, + "firstName" : "", + "lastName" : "", + "credentials" : [ { + "id" : "0d4049cb-293b-4f76-b82b-5ba9ee4530f2", + "type" : "password", + "userLabel" : "My password", + "createdDate" : 1708932779332, + "secretData" : "{\"value\":\"jNTZeVbTOOtuokGeXGhYd5Aa+G9TkCS1RFikWtULN/w=\",\"salt\":\"9St8VUxP3iiO2jZYM7whww==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "default-roles-basyx", "basyx-uploader-two" ], + "notBefore" : 0, + "groups" : [ ] + }, { + "id" : "c1a592bd-536f-4a3e-8193-c17d479814f3", + "createdTimestamp" : 1708934121191, + "username" : "basyx.uploader.3", + "enabled" : true, + "totp" : false, + "emailVerified" : false, + "firstName" : "", + "lastName" : "", + "credentials" : [ { + "id" : "7cd8fd3f-8c83-43b9-9fb7-6203e7c7376c", + "type" : "password", + "userLabel" : "My password", + "createdDate" : 1708934138452, + "secretData" : "{\"value\":\"BenBZDPAZWMjSh21uER8mw7PSEdXw8xRh7YPlWsNUno=\",\"salt\":\"ioGE5CqXT8XyCWkcbi9ETA==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ "basyx-uploader-three", "default-roles-basyx" ], + "notBefore" : 0, + "groups" : [ ] }, { "id" : "f3ec1793-3d62-41c3-ad34-b7b29ac88528", "createdTimestamp" : 1702030619322, @@ -1898,7 +1988,7 @@ "subType" : "anonymous", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "oidc-address-mapper", "oidc-full-name-mapper", "saml-user-property-mapper", "saml-user-attribute-mapper", "saml-role-list-mapper", "oidc-usermodel-property-mapper", "oidc-usermodel-attribute-mapper", "oidc-sha256-pairwise-sub-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-sha256-pairwise-sub-mapper", "saml-role-list-mapper", "oidc-usermodel-property-mapper", "oidc-full-name-mapper", "oidc-address-mapper", "saml-user-attribute-mapper", "saml-user-property-mapper", "oidc-usermodel-attribute-mapper" ] } }, { "id" : "7256d195-1e91-4f63-a9c4-6bef95243a92", @@ -1935,7 +2025,7 @@ "subType" : "authenticated", "subComponents" : { }, "config" : { - "allowed-protocol-mapper-types" : [ "oidc-usermodel-attribute-mapper", "oidc-sha256-pairwise-sub-mapper", "saml-user-attribute-mapper", "oidc-address-mapper", "oidc-usermodel-property-mapper", "saml-user-property-mapper", "saml-role-list-mapper", "oidc-full-name-mapper" ] + "allowed-protocol-mapper-types" : [ "oidc-sha256-pairwise-sub-mapper", "oidc-usermodel-attribute-mapper", "saml-user-property-mapper", "oidc-usermodel-property-mapper", "saml-role-list-mapper", "oidc-full-name-mapper", "oidc-address-mapper", "saml-user-attribute-mapper" ] } }, { "id" : "face2c9e-4d23-44e2-9a09-74e1d8448bd3",