I would like to secure my custom API in c8y microservices based on role-based access control, similar to how it currently works for the inventory role access in the platform.
Could you please guide me on how to implement role-based access for my API? Any documentation or examples would be greatly appreciated.
I’m assuming you are implementing a microservice using the java SDK?
The main concept to secure endpoints are global roles which are define in your manifest and must be implemented / checked in your code.
General aspects - Cumulocity documentation see “roles”.
@PreAuthorize("hasRole(YOUR_CUSTOM_ROLE)")
public ResponseEntity<?> someCustomControllerEndpoint(HttpServletRequest request) {
If you want to use inventory roles as permissions the only thing you need to do is to make sure that you are using the user beans (not using any global role) and not the tenant beans (using global access roles of manifest “required_roles”)
This example returns all devices the user has access to (and not ALL devices like the global access role would do)
@RestController
@RequestMapping("/devices")
public class DeviceController {
@Autowired
@Qualifier("userInventoryApi")
InventoryApi userInventoryApi;
@GetMapping(path = "/devicesUserScope", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<ManagedObjectRepresentation>> getAllDevicesUserScope() {
List<ManagedObjectRepresentation> managedObjectsList = new ArrayList<>();
InventoryFilter filter = new InventoryFilter().byFragmentType(IsDevice.class);
userInventoryApi.getManagedObjectsByFilter(filter).get().allPages().forEach(mor -> {
managedObjectsList.add(mor);
});
return new ResponseEntity<>(managedObjectsList, HttpStatus.OK);
}
}
With that you are in user scope and can only access data which is provided by the inventory roles for that specific user.
@Stefan_Witschel ,
Thanks stefan for the response , just wanted know more from coding point of view.
I have controller exposed in c8ymicroservice as:
@GetMapping(path = “/measurements/download”, produces = { MediaType.APPLICATION_OCTET_STREAM_VALUE,
MediaType.APPLICATION_JSON_VALUE })
public ResponseEntity<byte> downloadMeasurement(@RequestParam(“fileName”) String fileName) {
}
now i want to secure this API with as same as inventory roles, can i do it at controller level.
if yes…please explain what are the changes do i need to have in manifest file and in controller class, please provide any examples if available
As I have written already: If you want to use inventory roles to secure your endpoints you just need to use the user Qualifier & API in your controller implementation (see my example).
It will check the user permissions automatically and will return the data the user has access to defined via inventory roles.
If the user has no access the list will be probably empty or in your case the file will not contain any measurements.
Hi @Stefan_Witschel ,
i have tried the code snippet you given …but getting error as not within context.
but tried running within the context like:
@GetMapping(path = “/devicesUserScope”, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List> getAllDevicesUserScope() {
MicroserviceCredentials microserviceCredentials = contextService.getContext();
List<ManagedObjectRepresentation> managedObjectsList = new ArrayList<>();
contextService.runWithinContext(microserviceCredentials, () -> {
InventoryFilter filter = new InventoryFilter().byFragmentType("c8y_IsDevice");
userInventoryApi.getManagedObjectsByFilter(filter).get().allPages()
.forEach(mor -> {
managedObjectsList.add(mor);
});
});
return new ResponseEntity<>(managedObjectsList, HttpStatus.OK);
}
but still not getting any response..and getting error as not within context.can explain regarding the same
The context should be already given if you provide a RestController and send a request to it with an authenticated user you want to use to test this.
Find a working example here: cumulocity-microservice-templates/context/src/main/java/com/c8y/ms/templates/context/controller/DeviceController.java at main · Cumulocity-IoT/cumulocity-microservice-templates · GitHub
SubscriptionService or ContextService should be only necessary if you don’t get any external context like from Rest Request and do internal calls. But here the service user is used which will probably have global access so your inventory-role access will not work anymore, because there is no user-context available.
@Autowired
MicroserviceSubscriptionsService subscriptionsService;
@Autowired
InventoryApi inventoryApi;
public List<ManagedObjectRepresentation> getAllDevicesTenants() {
List<ManagedObjectRepresentation> managedObjectsList = new ArrayList<>();
InventoryFilter filter = new InventoryFilter().byFragmentType(IsDevice.class);
subscriptionsService.runForEachTenant( () -> {
inventoryApi.getManagedObjectsByFilter(filter).get().allPages().forEach(mor -> {
managedObjectsList.add(mor);
});
});
return managedObjectsList;
}
Hi @Stefan_Witschel ,
I have tried the code snippet which you given to run service based on UserContext in local..
By referrring the code from github.
But i am able to get the managedObject list only by running based on TenantContext i.e @Autowired InventoryApi inventoryApi;
from local and not by using @Qualifier(“userInventoryApi”)
is it necessary for me to deploy the service in Tenant to Enable User context.
Beacuse i m trying to access the Api from postMan using Basic Auth info username and password and running service locally which is not working.
But for tenant based context i am able to get devices by sending request from postman and running service locally.
when running service locally for userContext i.e by using
@Autowired
@Qualifier(“userInventoryApi”)
InventoryApi userInventoryApi;
and sending request from postman:
getting below error:
org.springframework.beans.factory.support.ScopeNotActiveException: Error creating bean with name ‘scopedTarget.userInventoryApi’: Scope ‘user’ is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: Not within any context!
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:383) ~[spring-beans-5.3.31.jar:5.3.31]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.31.jar:5.3.31]
please give some clarifications and explanation on the same.
If you provide a valid cumulocity user with tenantID as prefix you can also run it locally.
I shared working code with you already (see templates). Please check your code/project what you have defined/configured differently.