Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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}
Expand All @@ -54,6 +61,7 @@
*/
public class AuthorizedAasRegistryStorage implements AasRegistryStorage {

private static final Logger LOGGER = LoggerFactory.getLogger(AuthorizedAasRegistryStorage.class);
private AasRegistryStorage decorated;
private RbacPermissionResolver<AasRegistryTargetInformation> permissionResolver;

Expand All @@ -64,8 +72,33 @@ public AuthorizedAasRegistryStorage(AasRegistryStorage decorated, RbacPermission

@Override
public CursorResult<List<AssetAdministrationShellDescriptor>> 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<TargetInformation> targetInformations = permissionResolver.getMatchingTargetInformationInRules(Action.READ, new AasRegistryTargetInformation(getIdAsList(AasRegistryTargetPermissionVerifier.ALL_ALLOWED_WILDCARD)));

List<String> allIds = targetInformations.stream().map(AasRegistryTargetInformation.class::cast)
.map(AasRegistryTargetInformation::getAasIds).flatMap(List::stream).collect(Collectors.toList());

List<AssetAdministrationShellDescriptor> 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<String, AssetAdministrationShellDescriptor> aasMap = aasDescriptors.stream().collect(Collectors.toMap(AssetAdministrationShellDescriptor::getId, aas -> aas, (a, b) -> a, TreeMap::new));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is a TreeMap actually necessary here or would be a HashMap sufficient. This would improve performance.


PaginationSupport<AssetAdministrationShellDescriptor> paginationSupport = new PaginationSupport<>(aasMap, AssetAdministrationShellDescriptor::getId);

return paginationSupport.getPaged(pRequest);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,25 @@
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;
import org.eclipse.digitaltwin.aas4j.v3.model.Reference;
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}
Expand All @@ -51,6 +58,7 @@
*/
public class AuthorizedAasRepository implements AasRepository {

private static final Logger LOGGER = LoggerFactory.getLogger(AuthorizedAasRepository.class);
private AasRepository decorated;
private RbacPermissionResolver<AasTargetInformation> permissionResolver;

Expand All @@ -64,9 +72,31 @@ public AuthorizedAasRepository(AasRepository decorated, RbacPermissionResolver<A
public CursorResult<List<AssetAdministrationShell>> getAllAas(PaginationInfo pInfo) {
boolean isAuthorized = permissionResolver.hasPermission(Action.READ, new AasTargetInformation(getIdAsList("*")));

throwExceptionIfInsufficientPermission(isAuthorized);
if (isAuthorized)
return decorated.getAllAas(pInfo);

List<TargetInformation> targetInformations = permissionResolver.getMatchingTargetInformationInRules(Action.READ, new AasTargetInformation(getIdAsList("*")));

List<String> allIds = targetInformations.stream().map(AasTargetInformation.class::cast)
.map(AasTargetInformation::getAasIds).flatMap(List::stream).collect(Collectors.toList());

return decorated.getAllAas(pInfo);
List<AssetAdministrationShell> 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<String, AssetAdministrationShell> aasMap = shells.stream().collect(Collectors.toMap(AssetAdministrationShell::getId, aas -> aas, (a, b) -> a, TreeMap::new));

PaginationSupport<AssetAdministrationShell> paginationSupport = new PaginationSupport<>(aasMap, AssetAdministrationShell::getId);

return paginationSupport.getPaged(pInfo);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@

package org.eclipse.digitaltwin.basyx.authorization.rbac;

import java.util.List;

/**
* An interface for resolving Rbac permissions
*
Expand All @@ -34,4 +36,6 @@ public interface RbacPermissionResolver<T extends TargetInformation> {

public boolean hasPermission(Action action, T targetInformation);

public List<TargetInformation> getMatchingTargetInformationInRules(Action action, T targetInformation);

}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method assumes that rbacStorage::getRbacRule will always return a non-null RbacRule. If it returns null, calling getTargetInformation() on null will cause a NullPointerException.

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -79,6 +80,16 @@ public boolean hasPermission(final Action action, final T targetInformation) {

return matchingRule.isPresent();
}

public List<TargetInformation> getMatchingTargetInformationInRules(final Action action, final T targetInformation) {

List<String> roles = roleAuthenticator.getRoles();

List<RbacRule> 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<RbacRule> getMatchingRules(final List<String> roles, final Action action, final T targetInformation) {

Expand All @@ -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;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,25 @@
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;
import org.eclipse.digitaltwin.basyx.core.exceptions.InsufficientPermissionException;
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}
Expand All @@ -49,6 +56,7 @@
*/
public class AuthorizedConceptDescriptionRepository implements ConceptDescriptionRepository {

private static final Logger LOGGER = LoggerFactory.getLogger(AuthorizedConceptDescriptionRepository.class);
private ConceptDescriptionRepository decorated;
private RbacPermissionResolver<ConceptDescriptionTargetInformation> permissionResolver;

Expand All @@ -61,9 +69,31 @@ public AuthorizedConceptDescriptionRepository(ConceptDescriptionRepository decor
public CursorResult<List<ConceptDescription>> getAllConceptDescriptions(PaginationInfo pInfo) {
boolean isAuthorized = permissionResolver.hasPermission(Action.READ, new ConceptDescriptionTargetInformation(getIdAsList("*")));

throwExceptionIfInsufficientPermission(isAuthorized);
if (isAuthorized)
return decorated.getAllConceptDescriptions(pInfo);

List<TargetInformation> targetInformations = permissionResolver.getMatchingTargetInformationInRules(Action.READ, new ConceptDescriptionTargetInformation(getIdAsList("*")));

List<String> allIds = targetInformations.stream().map(ConceptDescriptionTargetInformation.class::cast)
.map(ConceptDescriptionTargetInformation::getConceptDescriptionIds).flatMap(List::stream).collect(Collectors.toList());

return decorated.getAllConceptDescriptions(pInfo);
List<ConceptDescription> 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<String, ConceptDescription> aasMap = conceptDesc.stream().collect(Collectors.toMap(ConceptDescription::getId, aas -> aas, (a, b) -> a, TreeMap::new));

PaginationSupport<ConceptDescription> paginationSupport = new PaginationSupport<>(aasMap, ConceptDescription::getId);

return paginationSupport.getPaged(pInfo);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -47,6 +55,7 @@
*/
public class AuthorizedSubmodelRegistryStorage implements SubmodelRegistryStorage {

private static final Logger LOGGER = LoggerFactory.getLogger(AuthorizedSubmodelRegistryStorage.class);
private SubmodelRegistryStorage decorated;
private RbacPermissionResolver<SubmodelRegistryTargetInformation> permissionResolver;

Expand All @@ -57,8 +66,34 @@ public AuthorizedSubmodelRegistryStorage(SubmodelRegistryStorage decorated, Rbac

@Override
public CursorResult<List<SubmodelDescriptor>> 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<TargetInformation> targetInformations = permissionResolver.getMatchingTargetInformationInRules(Action.READ, new SubmodelRegistryTargetInformation(getIdAsList(SubmodelRegistryTargetPermissionVerifier.ALL_ALLOWED_WILDCARD)));

List<String> allIds = targetInformations.stream().map(SubmodelRegistryTargetInformation.class::cast)
.map(SubmodelRegistryTargetInformation::getSubmodelIds).flatMap(List::stream).collect(Collectors.toList());

List<SubmodelDescriptor> 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<String, SubmodelDescriptor> aasMap = aasDescriptors.stream().collect(Collectors.toMap(SubmodelDescriptor::getId, aas -> aas, (a, b) -> a, TreeMap::new));

PaginationSupport<SubmodelDescriptor> paginationSupport = new PaginationSupport<>(aasMap, SubmodelDescriptor::getId);

return paginationSupport.getPaged(pRequest);
}

@Override
Expand Down
Loading
Loading