DataObject authorization based on metadata values

I am trying to set up authorization for data objects such that it would cascade in the following manner:

  • system user - full access
  • super user - full access (this would need to be audited)
  • admin user - high access limited by metadata (this would need to be audited)
  • object owner - full access (this would need to be audited)
  • object in data object tree owned by user - write access (this would need to be audited)
  • object in data object tree owned by another user - no access (this would need to be audited)
  • other - access limited by metadata (this would need to be audited)

I originally found DataObjectAuthorizationPolicyQueryBased at Sign in to LEAF. Basically the user would have a max level (U/S/TS, etc) that they can access and a field on the data object will have a corresponding marker (U/S/TS, etc) that dictates the level required. Is there a better example on how this would be used? How do I set this up? Where do I get the required metadata files that need to be upserted (SecurityGroup.xml, Role.xml, Permission.xml, and DataObjectQueryPermission.xml)? Where do I wire this in?

2 Likes

First and foremost: making a ticket for us to add some snippets like this in the LKB. You’re right that they are missing.

So, if you’re using a generated data-service, somewhere in your Application class, an IDataService will be built, and that’s where you inject your custom IDataObjectAuthorizationPolicy. I’m pasting below an example below that makes LEAF’s DataObjectAuthorizationPolicyQueryBased and imports the relevant metadata for LEAF’s authorization policies. Note that this includes all the BeanMetadata for every permission type, not just the query-based permission. But we will only honor permissions for policies you’re actually using. You can combine multiple policies to use multiple permissions using DataObjectAuthorizationPolicy, which is a composite policy.

IEventService eventService = EventServiceLocal.newBuilder()
        .build();
eventService.start();

// This AtomicReference approach lets us use the same services we're potentially protecting to store authz DataObjects and queries
AtomicReference<IDataObjectService> dataObjectServiceRef = new AtomicReference<>();
AtomicReference<IDataObjectQueryService> queryServiceRef = new AtomicReference<>();
AtomicReference<IDataObjectQueryEvaluationService> queryEvalServiceRef = new AtomicReference<>();

IDataObjectAuthorizationPolicy authorizationPolicy = DataObjectAuthorizationPolicyQueryBased
        .newBuilder(dataObjectServiceRef, queryServiceRef, queryEvalServiceRef)
        .build();

IDataService dataService = DataServiceTransient.newBuilder(eventService)
        .withDataObjectAuthorizationPolicy(authorizationPolicy)
        .build();

dataObjectServiceRef.set(dataService.getDataObjectService());
queryServiceRef.set(dataService.getDataObjectQueryService());
queryEvalServiceRef.set(dataService.getDataObjectQueryEvaluationService());

dataService.upsertResources(CoreResourceDescriptors.AUTHORIZATION, Context.makeSystemContext());

If you’re on a version pre-3.3.0, use AuthorizationResourceLocations.INSTANCE instead of the CoreResourceDescriptors.AUTHORIZATION value.

From there you can start adding Role DataObjects, putting DataObjectQueryPermission DataObjects as children under the Role DataObjects (ie DataObjectTrees as the role-permission relationship is parent-child). Then once you have your Role DataObjects, you can start making SecurityGroup DataObjects, which reference one or more Role DataObject IDs in a SetOfStringsField. You can add these DataObjects however you want: from Java code, storing in a file and parsing and adding, from a UI like LEAF’s Admin App, or whatever.

The only tricky thing with DataObjectQueryPermissions is you need to first persist the DataObjectQuery in the DataObjectQueryService you provide when constructing the authz policy, bc what you place on the DataObjectQueryPermission is the ID of the persisted DataObjectQuery.

So here’s an example of making a DataObjectQuery, persisting it, making the Role, DataObjectQueryPermission, and SecurityGroup DataObjects, persisting those, and then making a User that has the permissions given by that SecurityGroup.

DataObjectQuery query = dataObjectQuery(types("ProtectedResource"), stringEquals("classification", "U"));
query = getOnlyElement(dataService.getDataObjectQueryService()
        .upsert(List.of(query), Context.makeSystemContext()));

IDataObjectService dataObjectService = dataService.getDataObjectService();

DataObject role = DataObject.newBuilder()
        .withType(RoleMetadataConstants.TYPE)
        .with(RoleMetadataConstants.NAME, "Unclassified-View-Role")
        .build();
role = getOnlyElement(dataObjectService.addRoots(List.of(role), Context.makeSystemContext()));

DataObject dataObjectQueryPermission = DataObject.newBuilder()
        .withType(DataObjectQueryPermissionMetadataConstants.TYPE)
        .with(DataObjectQueryPermissionMetadataConstants.DATA_OBJECT_QUERY_ID, query.getId())
        .with(DataObjectQueryPermissionMetadataConstants.CAN_ADD, false)
        .with(DataObjectQueryPermissionMetadataConstants.CAN_READ, true)
        .with(DataObjectQueryPermissionMetadataConstants.CAN_UPDATE, false)
        .with(DataObjectQueryPermissionMetadataConstants.CAN_DELETE, false)
        .build();
dataObjectService.addChildren(List.of(dataObjectQueryPermission), role.getId(), Context.makeSystemContext());

DataObject group = DataObject.newBuilder()
        .withType(SecurityGroupMetadataConstants.TYPE)
        .with(SecurityGroupMetadataConstants.NAME, "Unclassified-View-Group")
        .with(SecurityGroupMetadataConstants.ROLE_IDS, Set.of(role.getId()))
        .build();
group = getOnlyElement(dataObjectService.addRoots(List.of(group), Context.makeSystemContext()));

User user = User.newBuilder()
        .withUsername("blake.doe")
        .withAuthorizationProp(Sets.newHashSet(group.getId()))
        .build();

Also here are my imports if they’re helpful to anyone!

import static com.google.common.collect.Iterables.getOnlyElement;
import static com.leidos.leaf.beans.query._static.api.DataObjectQueries.dataObjectQuery;
import static com.leidos.leaf.beans.query._static.api.Filters.stringEquals;
import static com.leidos.leaf.beans.query._static.api.FiltersTypeSafe.types;

import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

import com.google.common.collect.Sets;
import com.leidos.leaf.beans.Context;
import com.leidos.leaf.beans.dataobject.DataObject;
import com.leidos.leaf.beans.query.DataObjectQuery;
import com.leidos.leaf.beans.user.User;
import com.leidos.leaf.data.service.DataServiceTransient;
import com.leidos.leaf.data.service.IDataService;
import com.leidos.leaf.dataobject.service.IDataObjectService;
import com.leidos.leaf.dataobject.service.authorization.policy.DataObjectAuthorizationPolicyQueryBased;
import com.leidos.leaf.dataobject.service.authorization.policy.IDataObjectAuthorizationPolicy;
import com.leidos.leaf.dataobjectquery.evaluation.IDataObjectQueryEvaluationService;
import com.leidos.leaf.dataobjectquery.service.IDataObjectQueryService;
import com.leidos.leaf.event.service.EventServiceLocal;
import com.leidos.leaf.event.service.IEventService;
import com.leidos.leaf.util.resource.CoreResourceDescriptors;