diff --git a/basyx.aasregistry/basyx.aasregistry-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/authorization/AuthorizedAasRegistryStorage.java b/basyx.aasregistry/basyx.aasregistry-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/authorization/AuthorizedAasRegistryStorage.java index acacfbeb4..523be951e 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/authorization/AuthorizedAasRegistryStorage.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/feature/authorization/AuthorizedAasRegistryStorage.java @@ -28,7 +28,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.Set; +import java.util.TreeMap; +import java.util.stream.Collectors; import org.eclipse.digitaltwin.basyx.aasregistry.feature.authorization.rbac.AasRegistryTargetPermissionVerifier; import org.eclipse.digitaltwin.basyx.aasregistry.model.AssetAdministrationShellDescriptor; @@ -43,9 +46,13 @@ import org.eclipse.digitaltwin.basyx.aasregistry.service.storage.DescriptorFilter; import org.eclipse.digitaltwin.basyx.authorization.rbac.Action; import org.eclipse.digitaltwin.basyx.authorization.rbac.RbacPermissionResolver; +import org.eclipse.digitaltwin.basyx.authorization.rbac.TargetInformation; import org.eclipse.digitaltwin.basyx.core.exceptions.InsufficientPermissionException; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationSupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Decorator for authorized {@link AasRegistryStorage} @@ -54,6 +61,7 @@ */ public class AuthorizedAasRegistryStorage implements AasRegistryStorage { + private static final Logger LOGGER = LoggerFactory.getLogger(AuthorizedAasRegistryStorage.class); private AasRegistryStorage decorated; private RbacPermissionResolver permissionResolver; @@ -64,8 +72,33 @@ public AuthorizedAasRegistryStorage(AasRegistryStorage decorated, RbacPermission @Override public CursorResult> getAllAasDescriptors(PaginationInfo pRequest, DescriptorFilter filter) { - assertHasPermission(Action.READ, getIdAsList(AasRegistryTargetPermissionVerifier.ALL_ALLOWED_WILDCARD)); - return decorated.getAllAasDescriptors(pRequest, filter); + boolean isAuthorized = permissionResolver.hasPermission(Action.READ, new AasRegistryTargetInformation(getIdAsList(AasRegistryTargetPermissionVerifier.ALL_ALLOWED_WILDCARD))); + + if (isAuthorized) + return decorated.getAllAasDescriptors(pRequest, filter); + + List targetInformations = permissionResolver.getMatchingTargetInformationInRules(Action.READ, new AasRegistryTargetInformation(getIdAsList(AasRegistryTargetPermissionVerifier.ALL_ALLOWED_WILDCARD))); + + List allIds = targetInformations.stream().map(AasRegistryTargetInformation.class::cast) + .map(AasRegistryTargetInformation::getAasIds).flatMap(List::stream).collect(Collectors.toList()); + + List aasDescriptors = allIds.stream().map(id -> { + try { + return getAasDescriptor(id); + } catch (AasDescriptorNotFoundException e) { + LOGGER.error("AAS Descriptor: '{}' not found, Error: {}", id, e.getMessage()); + return null; + } catch (Exception e) { + LOGGER.error("Exception occurred while retrieving the AAS Descriptor: {}, Error: {}", id, e.getMessage()); + return null; + } + }).filter(Objects::nonNull).collect(Collectors.toList()); + + TreeMap aasMap = aasDescriptors.stream().collect(Collectors.toMap(AssetAdministrationShellDescriptor::getId, aas -> aas, (a, b) -> a, TreeMap::new)); + + PaginationSupport paginationSupport = new PaginationSupport<>(aasMap, AssetAdministrationShellDescriptor::getId); + + return paginationSupport.getPaged(pRequest); } @Override diff --git a/basyx.aasregistry/basyx.aasregistry-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/regression/feature/authorization/TestAuthorizedAasRegistry.java b/basyx.aasregistry/basyx.aasregistry-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/regression/feature/authorization/TestAuthorizedAasRegistry.java index e233ab306..31d3f5b02 100644 --- a/basyx.aasregistry/basyx.aasregistry-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/regression/feature/authorization/TestAuthorizedAasRegistry.java +++ b/basyx.aasregistry/basyx.aasregistry-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasregistry/regression/feature/authorization/TestAuthorizedAasRegistry.java @@ -144,7 +144,7 @@ public void getAllAasDescriptorsWithInsufficientPermissionRole() throws IOExcept String accessToken = tokenProvider.getAccessToken(dummyCredential.getUsername(), dummyCredential.getPassword()); CloseableHttpResponse retrievalResponse = getAllElementsWithAuthorization(aasRegistryBaseUrl, accessToken); - assertEquals(HttpStatus.FORBIDDEN.value(), retrievalResponse.getCode()); + assertEquals(HttpStatus.OK.value(), retrievalResponse.getCode()); } @Test diff --git a/basyx.aasregistry/basyx.aasregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/integration/AuthorizedAasRegistryTestSuite.java b/basyx.aasregistry/basyx.aasregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/integration/AuthorizedAasRegistryTestSuite.java index 8403d5cec..3e825e63b 100644 --- a/basyx.aasregistry/basyx.aasregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/integration/AuthorizedAasRegistryTestSuite.java +++ b/basyx.aasregistry/basyx.aasregistry-service-basetests/src/main/java/org/eclipse/digitaltwin/basyx/aasregistry/service/tests/integration/AuthorizedAasRegistryTestSuite.java @@ -134,7 +134,7 @@ public void getAllAasDescriptorsWithInsufficientPermissionRole() throws IOExcept String accessToken = getAccessTokenProvider().getAccessToken(dummyCredential.getUsername(), dummyCredential.getPassword()); CloseableHttpResponse retrievalResponse = getAllElementsWithAuthorization(aasRegistryBaseUrl, accessToken); - assertEquals(HttpStatus.FORBIDDEN.value(), retrievalResponse.getCode()); + assertEquals(HttpStatus.OK.value(), retrievalResponse.getCode()); } @Test diff --git a/basyx.aasrepository/basyx.aasrepository-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/authorization/AuthorizedAasRepository.java b/basyx.aasrepository/basyx.aasrepository-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/authorization/AuthorizedAasRepository.java index a4f128864..ce17eba7d 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/authorization/AuthorizedAasRepository.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/authorization/AuthorizedAasRepository.java @@ -30,6 +30,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; +import java.util.TreeMap; +import java.util.stream.Collectors; import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; import org.eclipse.digitaltwin.aas4j.v3.model.AssetInformation; @@ -37,11 +40,15 @@ import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository; import org.eclipse.digitaltwin.basyx.authorization.rbac.Action; import org.eclipse.digitaltwin.basyx.authorization.rbac.RbacPermissionResolver; +import org.eclipse.digitaltwin.basyx.authorization.rbac.TargetInformation; import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; import org.eclipse.digitaltwin.basyx.core.exceptions.InsufficientPermissionException; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationSupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Decorator for authorized {@link AasRepository} @@ -51,6 +58,7 @@ */ public class AuthorizedAasRepository implements AasRepository { + private static final Logger LOGGER = LoggerFactory.getLogger(AuthorizedAasRepository.class); private AasRepository decorated; private RbacPermissionResolver permissionResolver; @@ -64,9 +72,31 @@ public AuthorizedAasRepository(AasRepository decorated, RbacPermissionResolver> getAllAas(PaginationInfo pInfo) { boolean isAuthorized = permissionResolver.hasPermission(Action.READ, new AasTargetInformation(getIdAsList("*"))); - throwExceptionIfInsufficientPermission(isAuthorized); + if (isAuthorized) + return decorated.getAllAas(pInfo); + + List targetInformations = permissionResolver.getMatchingTargetInformationInRules(Action.READ, new AasTargetInformation(getIdAsList("*"))); + + List allIds = targetInformations.stream().map(AasTargetInformation.class::cast) + .map(AasTargetInformation::getAasIds).flatMap(List::stream).collect(Collectors.toList()); - return decorated.getAllAas(pInfo); + List shells = allIds.stream().map(id -> { + try { + return getAas(id); + } catch (ElementDoesNotExistException e) { + LOGGER.error("AAS: '{}' not found, Error: {}", id, e.getMessage()); + return null; + } catch (Exception e) { + LOGGER.error("Exception occurred while retrieving the AAS: {}, Error: {}", id, e.getMessage()); + return null; + } + }).filter(Objects::nonNull).collect(Collectors.toList()); + + TreeMap aasMap = shells.stream().collect(Collectors.toMap(AssetAdministrationShell::getId, aas -> aas, (a, b) -> a, TreeMap::new)); + + PaginationSupport paginationSupport = new PaginationSupport<>(aasMap, AssetAdministrationShell::getId); + + return paginationSupport.getPaged(pInfo); } @Override diff --git a/basyx.aasrepository/basyx.aasrepository-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/authorization/TestAuthorizedAasRepository.java b/basyx.aasrepository/basyx.aasrepository-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/authorization/TestAuthorizedAasRepository.java index 83c561788..9d08021b4 100644 --- a/basyx.aasrepository/basyx.aasrepository-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/authorization/TestAuthorizedAasRepository.java +++ b/basyx.aasrepository/basyx.aasrepository-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/aasrepository/feature/authorization/TestAuthorizedAasRepository.java @@ -131,7 +131,7 @@ public void getAllAasWithInsufficientPermissionRole() throws IOException { String accessToken = tokenProvider.getAccessToken(dummyCredential.getUsername(), dummyCredential.getPassword()); CloseableHttpResponse retrievalResponse = getAllAasWithAuthorization(accessToken); - assertEquals(HttpStatus.FORBIDDEN.value(), retrievalResponse.getCode()); + assertEquals(HttpStatus.OK.value(), retrievalResponse.getCode()); } @Test diff --git a/basyx.common/basyx.authorization/src/main/java/org/eclipse/digitaltwin/basyx/authorization/rbac/RbacPermissionResolver.java b/basyx.common/basyx.authorization/src/main/java/org/eclipse/digitaltwin/basyx/authorization/rbac/RbacPermissionResolver.java index d8165b807..bc697415e 100644 --- a/basyx.common/basyx.authorization/src/main/java/org/eclipse/digitaltwin/basyx/authorization/rbac/RbacPermissionResolver.java +++ b/basyx.common/basyx.authorization/src/main/java/org/eclipse/digitaltwin/basyx/authorization/rbac/RbacPermissionResolver.java @@ -25,6 +25,8 @@ package org.eclipse.digitaltwin.basyx.authorization.rbac; +import java.util.List; + /** * An interface for resolving Rbac permissions * @@ -34,4 +36,6 @@ public interface RbacPermissionResolver { public boolean hasPermission(Action action, T targetInformation); + public List getMatchingTargetInformationInRules(Action action, T targetInformation); + } diff --git a/basyx.common/basyx.authorization/src/main/java/org/eclipse/digitaltwin/basyx/authorization/rbac/SimpleRbacPermissionResolver.java b/basyx.common/basyx.authorization/src/main/java/org/eclipse/digitaltwin/basyx/authorization/rbac/SimpleRbacPermissionResolver.java index f2b4be32c..de85ee94d 100644 --- a/basyx.common/basyx.authorization/src/main/java/org/eclipse/digitaltwin/basyx/authorization/rbac/SimpleRbacPermissionResolver.java +++ b/basyx.common/basyx.authorization/src/main/java/org/eclipse/digitaltwin/basyx/authorization/rbac/SimpleRbacPermissionResolver.java @@ -26,6 +26,7 @@ package org.eclipse.digitaltwin.basyx.authorization.rbac; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -79,6 +80,16 @@ public boolean hasPermission(final Action action, final T targetInformation) { return matchingRule.isPresent(); } + + public List getMatchingTargetInformationInRules(final Action action, final T targetInformation) { + + List roles = roleAuthenticator.getRoles(); + + List filteredRbacRulesForTargetInfos = roles.stream().map(role -> generateKey(role, action.toString(), targetInformation.getClass().getName())).filter(Objects::nonNull).filter(this::exist).map(this::getRbacRule) + .filter(Objects::nonNull).collect(Collectors.toList()); + + return filteredRbacRulesForTargetInfos.stream().map(rbacRule -> rbacRule.getTargetInformation()).collect(Collectors.toList()); + } private Stream getMatchingRules(final List roles, final Action action, final T targetInformation) { @@ -92,4 +103,31 @@ private boolean checkRbacRuleMatchesTargetInfo(final RbacRule rbacRule, final T return targetPermissionVerifier.isVerified(rbacRule, targetInformation); } + private String generateKey(String role, String action, String targetClassName) { + try { + return RbacRuleKeyGenerator.generateKey(role, action, targetClassName); + } catch (Exception e) { + logger.error("Error generating Rbac Rule Key for role: '{}' Action: '{}' and Target Information Class: '{}'", role, action, targetClassName, e); + return null; + } + } + + private boolean exist(String key) { + try { + return rbacStorage.exist(key); + } catch (Exception e) { + logger.error("Error checking existence of Rbac Rule for key: {}", key, e); + return false; + } + } + + private RbacRule getRbacRule(String key) { + try { + return rbacStorage.getRbacRule(key); + } catch (Exception e) { + logger.error("Error retrieving the Rbac Rule for key: {}", key, e); + return null; + } + } + } diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/authorization/AuthorizedConceptDescriptionRepository.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/authorization/AuthorizedConceptDescriptionRepository.java index ec181040c..60ecd1bd6 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/authorization/AuthorizedConceptDescriptionRepository.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/authorization/AuthorizedConceptDescriptionRepository.java @@ -28,11 +28,15 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; +import java.util.TreeMap; +import java.util.stream.Collectors; import org.eclipse.digitaltwin.aas4j.v3.model.ConceptDescription; import org.eclipse.digitaltwin.aas4j.v3.model.Reference; import org.eclipse.digitaltwin.basyx.authorization.rbac.Action; import org.eclipse.digitaltwin.basyx.authorization.rbac.RbacPermissionResolver; +import org.eclipse.digitaltwin.basyx.authorization.rbac.TargetInformation; import org.eclipse.digitaltwin.basyx.conceptdescriptionrepository.ConceptDescriptionRepository; import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; @@ -40,6 +44,9 @@ import org.eclipse.digitaltwin.basyx.core.exceptions.MissingIdentifierException; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationSupport; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Decorator for authorized {@link ConceptDescriptionRepository} @@ -49,6 +56,7 @@ */ public class AuthorizedConceptDescriptionRepository implements ConceptDescriptionRepository { + private static final Logger LOGGER = LoggerFactory.getLogger(AuthorizedConceptDescriptionRepository.class); private ConceptDescriptionRepository decorated; private RbacPermissionResolver permissionResolver; @@ -61,9 +69,31 @@ public AuthorizedConceptDescriptionRepository(ConceptDescriptionRepository decor public CursorResult> getAllConceptDescriptions(PaginationInfo pInfo) { boolean isAuthorized = permissionResolver.hasPermission(Action.READ, new ConceptDescriptionTargetInformation(getIdAsList("*"))); - throwExceptionIfInsufficientPermission(isAuthorized); + if (isAuthorized) + return decorated.getAllConceptDescriptions(pInfo); + + List targetInformations = permissionResolver.getMatchingTargetInformationInRules(Action.READ, new ConceptDescriptionTargetInformation(getIdAsList("*"))); + + List allIds = targetInformations.stream().map(ConceptDescriptionTargetInformation.class::cast) + .map(ConceptDescriptionTargetInformation::getConceptDescriptionIds).flatMap(List::stream).collect(Collectors.toList()); - return decorated.getAllConceptDescriptions(pInfo); + List conceptDesc = allIds.stream().map(id -> { + try { + return getConceptDescription(id); + } catch (ElementDoesNotExistException e) { + LOGGER.error("Concept Description: '{}' not found, Error: {}", id, e.getMessage()); + return null; + } catch (Exception e) { + LOGGER.error("Exception occurred while retrieving the Concept Description: {}, Error: {}", id, e.getMessage()); + return null; + } + }).filter(Objects::nonNull).collect(Collectors.toList()); + + TreeMap aasMap = conceptDesc.stream().collect(Collectors.toMap(ConceptDescription::getId, aas -> aas, (a, b) -> a, TreeMap::new)); + + PaginationSupport paginationSupport = new PaginationSupport<>(aasMap, ConceptDescription::getId); + + return paginationSupport.getPaged(pInfo); } @Override diff --git a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/authorization/TestAuthorizedCDRepository.java b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/authorization/TestAuthorizedCDRepository.java index 7956a08ad..80befd8b2 100644 --- a/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/authorization/TestAuthorizedCDRepository.java +++ b/basyx.conceptdescriptionrepository/basyx.conceptdescriptionrepository-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/conceptdescriptionrepository/feature/authorization/TestAuthorizedCDRepository.java @@ -113,7 +113,7 @@ public void getAllCDWithInsufficientPermissionRole() throws IOException { String accessToken = tokenProvider.getAccessToken(dummyCredential.getUsername(), dummyCredential.getPassword()); CloseableHttpResponse retrievalResponse = getAllCDWithAuthorization(accessToken); - assertEquals(HttpStatus.FORBIDDEN.value(), retrievalResponse.getCode()); + assertEquals(HttpStatus.OK.value(), retrievalResponse.getCode()); } @Test diff --git a/basyx.submodelregistry/basyx.submodelregistry-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/authorization/AuthorizedSubmodelRegistryStorage.java b/basyx.submodelregistry/basyx.submodelregistry-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/authorization/AuthorizedSubmodelRegistryStorage.java index 74887d22e..d954aaf66 100644 --- a/basyx.submodelregistry/basyx.submodelregistry-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/authorization/AuthorizedSubmodelRegistryStorage.java +++ b/basyx.submodelregistry/basyx.submodelregistry-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/submodelregistry/feature/authorization/AuthorizedSubmodelRegistryStorage.java @@ -28,17 +28,25 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.Set; +import java.util.TreeMap; +import java.util.stream.Collectors; + import org.eclipse.digitaltwin.basyx.authorization.rbac.Action; import org.eclipse.digitaltwin.basyx.authorization.rbac.RbacPermissionResolver; +import org.eclipse.digitaltwin.basyx.authorization.rbac.TargetInformation; import org.eclipse.digitaltwin.basyx.core.exceptions.InsufficientPermissionException; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationSupport; import org.eclipse.digitaltwin.basyx.submodelregistry.feature.authorization.rbac.SubmodelRegistryTargetPermissionVerifier; import org.eclipse.digitaltwin.basyx.submodelregistry.model.SubmodelDescriptor; import org.eclipse.digitaltwin.basyx.submodelregistry.service.errors.SubmodelAlreadyExistsException; import org.eclipse.digitaltwin.basyx.submodelregistry.service.errors.SubmodelNotFoundException; import org.eclipse.digitaltwin.basyx.submodelregistry.service.storage.SubmodelRegistryStorage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Decorator for authorized {@link SubmodelRegistryStorage} @@ -47,6 +55,7 @@ */ public class AuthorizedSubmodelRegistryStorage implements SubmodelRegistryStorage { + private static final Logger LOGGER = LoggerFactory.getLogger(AuthorizedSubmodelRegistryStorage.class); private SubmodelRegistryStorage decorated; private RbacPermissionResolver permissionResolver; @@ -57,8 +66,34 @@ public AuthorizedSubmodelRegistryStorage(SubmodelRegistryStorage decorated, Rbac @Override public CursorResult> getAllSubmodelDescriptors(PaginationInfo pRequest) { - assertHasPermission(Action.READ, getIdAsList(SubmodelRegistryTargetPermissionVerifier.ALL_ALLOWED_WILDCARD)); - return decorated.getAllSubmodelDescriptors(pRequest); + boolean isAuthorized = permissionResolver.hasPermission(Action.READ, new SubmodelRegistryTargetInformation(getIdAsList(SubmodelRegistryTargetPermissionVerifier.ALL_ALLOWED_WILDCARD))); + throwExceptionIfInsufficientPermission(isAuthorized); + + if (isAuthorized) + return decorated.getAllSubmodelDescriptors(pRequest); + + List targetInformations = permissionResolver.getMatchingTargetInformationInRules(Action.READ, new SubmodelRegistryTargetInformation(getIdAsList(SubmodelRegistryTargetPermissionVerifier.ALL_ALLOWED_WILDCARD))); + + List allIds = targetInformations.stream().map(SubmodelRegistryTargetInformation.class::cast) + .map(SubmodelRegistryTargetInformation::getSubmodelIds).flatMap(List::stream).collect(Collectors.toList()); + + List aasDescriptors = allIds.stream().map(id -> { + try { + return getSubmodelDescriptor(id); + } catch (SubmodelNotFoundException e) { + LOGGER.error("Submodel Descriptor: '{}' not found, Error: {}", id, e.getMessage()); + return null; + } catch (Exception e) { + LOGGER.error("Exception occurred while retrieving the Submodel Descriptor: {}, Error: {}", id, e.getMessage()); + return null; + } + }).filter(Objects::nonNull).collect(Collectors.toList()); + + TreeMap aasMap = aasDescriptors.stream().collect(Collectors.toMap(SubmodelDescriptor::getId, aas -> aas, (a, b) -> a, TreeMap::new)); + + PaginationSupport paginationSupport = new PaginationSupport<>(aasMap, SubmodelDescriptor::getId); + + return paginationSupport.getPaged(pRequest); } @Override diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/authorization/AuthorizedSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/authorization/AuthorizedSubmodelRepository.java index 2b94ecaac..37a068043 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/authorization/AuthorizedSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-authorization/src/main/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/authorization/AuthorizedSubmodelRepository.java @@ -30,12 +30,16 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; +import java.util.TreeMap; +import java.util.stream.Collectors; import org.eclipse.digitaltwin.aas4j.v3.model.OperationVariable; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; import org.eclipse.digitaltwin.basyx.authorization.rbac.Action; import org.eclipse.digitaltwin.basyx.authorization.rbac.RbacPermissionResolver; +import org.eclipse.digitaltwin.basyx.authorization.rbac.TargetInformation; import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException; import org.eclipse.digitaltwin.basyx.core.exceptions.ElementNotAFileException; @@ -43,9 +47,12 @@ import org.eclipse.digitaltwin.basyx.core.exceptions.InsufficientPermissionException; import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult; import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo; +import org.eclipse.digitaltwin.basyx.core.pagination.PaginationSupport; import org.eclipse.digitaltwin.basyx.submodelrepository.SubmodelRepository; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelElementValue; import org.eclipse.digitaltwin.basyx.submodelservice.value.SubmodelValueOnly; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Decorator for authorized {@link SubmodelRepository} @@ -55,6 +62,7 @@ */ public class AuthorizedSubmodelRepository implements SubmodelRepository { + private static final Logger LOGGER = LoggerFactory.getLogger(AuthorizedSubmodelRepository.class); private static final String ALL_ALLOWED_WILDCARD = "*"; private SubmodelRepository decorated; private RbacPermissionResolver permissionResolver; @@ -68,9 +76,31 @@ public AuthorizedSubmodelRepository(SubmodelRepository decorated, RbacPermission public CursorResult> getAllSubmodels(PaginationInfo pInfo) { boolean isAuthorized = permissionResolver.hasPermission(Action.READ, new SubmodelTargetInformation(getIdAsList(ALL_ALLOWED_WILDCARD), getIdAsList(ALL_ALLOWED_WILDCARD))); - throwExceptionIfInsufficientPermission(isAuthorized); - - return decorated.getAllSubmodels(pInfo); + if (isAuthorized) + return decorated.getAllSubmodels(pInfo); + + List targetInformations = permissionResolver.getMatchingTargetInformationInRules(Action.READ, new SubmodelTargetInformation(getIdAsList(ALL_ALLOWED_WILDCARD), getIdAsList(ALL_ALLOWED_WILDCARD))); + + List allIds = targetInformations.stream().map(SubmodelTargetInformation.class::cast) + .map(SubmodelTargetInformation::getSubmodelIds).flatMap(List::stream).collect(Collectors.toList()); + + List submodels = allIds.stream().map(id -> { + try { + return getSubmodel(id); + } catch (ElementDoesNotExistException e) { + LOGGER.error("Submodel: '{}' not found, Error: {}", id, e.getMessage()); + return null; + } catch (Exception e) { + LOGGER.error("Exception occurred while retrieving the Submodel: {}, Error: {}", id, e.getMessage()); + return null; + } + }).filter(Objects::nonNull).collect(Collectors.toList()); + + TreeMap aasMap = submodels.stream().collect(Collectors.toMap(Submodel::getId, aas -> aas, (a, b) -> a, TreeMap::new)); + + PaginationSupport paginationSupport = new PaginationSupport<>(aasMap, Submodel::getId); + + return paginationSupport.getPaged(pInfo); } @Override @@ -122,9 +152,29 @@ public void deleteSubmodel(String submodelId) throws ElementDoesNotExistExceptio public CursorResult> getSubmodelElements(String submodelId, PaginationInfo pInfo) throws ElementDoesNotExistException { boolean isAuthorized = permissionResolver.hasPermission(Action.READ, new SubmodelTargetInformation(getIdAsList(submodelId), getIdAsList(ALL_ALLOWED_WILDCARD))); - throwExceptionIfInsufficientPermission(isAuthorized); - - return decorated.getSubmodelElements(submodelId, pInfo); + if (isAuthorized) + return decorated.getSubmodelElements(submodelId, pInfo); + + getSubmodel(submodelId); + + List targetInformations = permissionResolver.getMatchingTargetInformationInRules(Action.READ, new SubmodelTargetInformation(getIdAsList(submodelId), getIdAsList(ALL_ALLOWED_WILDCARD))); + + List allIds = targetInformations.stream().map(SubmodelTargetInformation.class::cast) + .map(SubmodelTargetInformation::getSubmodelElementIdShortPaths).flatMap(List::stream).collect(Collectors.toList()); + + List smes = allIds.stream().map(id -> { + try { + return getSubmodelElement(submodelId, id); + } catch (Exception e) { + return null; + } + }).filter(Objects::nonNull).collect(Collectors.toList()); + + TreeMap aasMap = smes.stream().collect(Collectors.toMap(SubmodelElement::getIdShort, aas -> aas, (a, b) -> a, TreeMap::new)); + + PaginationSupport paginationSupport = new PaginationSupport<>(aasMap, SubmodelElement::getIdShort); + + return paginationSupport.getPaged(pInfo); } @Override diff --git a/basyx.submodelrepository/basyx.submodelrepository-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/authorization/TestAuthorizedSubmodelRepository.java b/basyx.submodelrepository/basyx.submodelrepository-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/authorization/TestAuthorizedSubmodelRepository.java index b65f1e8ce..085eb545a 100644 --- a/basyx.submodelrepository/basyx.submodelrepository-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/authorization/TestAuthorizedSubmodelRepository.java +++ b/basyx.submodelrepository/basyx.submodelrepository-feature-authorization/src/test/java/org/eclipse/digitaltwin/basyx/submodelrepository/feature/authorization/TestAuthorizedSubmodelRepository.java @@ -132,7 +132,7 @@ public void getAllSubmodelsWithInsufficientPermissionRole() throws IOException { String accessToken = tokenProvider.getAccessToken(dummyCredential.getUsername(), dummyCredential.getPassword()); CloseableHttpResponse retrievalResponse = getAllSubmodelsWithAuthorization(accessToken); - assertEquals(HttpStatus.FORBIDDEN.value(), retrievalResponse.getCode()); + assertEquals(HttpStatus.OK.value(), retrievalResponse.getCode()); } @Test