AutoMapper Support

OPADotNet has support for mapping policy results into boolean variables with the help of AutoMapper and the nuget package “OPADotNet.Automapper”.

This can be used for scenarios where one wants to create an API to get the current users permission on an object/objects.

Setup

To start using the AutoMapper support, the nuget package OPADotNet.Automapper must be installed. Afterwards it should be added while configure services:

// Configure AutoMapper first since OPADotNet adds a decorator infront of AutoMapper.
services.AddAutoMapper(...);

services.AddOpa(opt =>
{
    // Add auto mapper support
    opt.AddAutomapperSupport();
    ...
});

It is important that AutoMapper is configured before adding OPA AutoMapper support since it adds a decorator infront of IMapper.

Custom user provider

The default user provider uses the current ClaimsPrincipal from the HttpContext. It is possible to override this behaviour by implementing the interface IOpaAutoMapperUserProvider.

Example:

class CustomUserProvider : IOpaAutoMapperUserProvider
{
    public ClaimsPrincipal GetClaimsPrincipal()
    {
        //Return a custom user provider here
        return new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>()
        {
            new Claim("name", "test")
        }));
    }
}

To use the custom provider, OPA is configured in the following way:

services.AddOpa(opt =>
{
    // Add auto mapper support
    opt.AddAutomapperSupport<CustomUserProvider>();
    ...
});

The provided class is added as a scoped service.

Configure an AutoMapper mapping

There are two different methods that can be used when configuring the AutoMapper mappings:

  • MapFromPolicy - Maps a value from a policy name, uses the source object on the Policy. So if one maps from type A to type B, type A will be used with the policy.

  • MapFromPolicyBasedOnDestination - Maps a value from a policy name, but instead uses the destination object on the Policy.

Example for MapFromPolicy:

// Policy
package test

allow {
    data.testdata[_].dbname = ""test""
}

//Classes
private class DbModel
{
    public string DbName { get; set; }
}

private class Model
{
    public string Name { get; set; }

    public bool PolicyValue { get; set; }
}

// AutoMapper mapping
CreateMap<DbModel, Model>(AutoMapper.MemberList.Destination)
  .ForMember(x => x.Name, opt => opt.MapFrom(src => src.DbName))
  .ForMember(x => x.PolicyValue, opt => opt.MapFromPolicy("test"))

In the example above, the property names of the DbModel will be used and also the values in the DbModel.

Example for MapFromPolicyBasedOnDestination:

// Policy
package test

allow {
    data.testdata[_].name = ""test""
}

//Classes
private class DbModel
{
    public string DbName { get; set; }
}

private class Model
{
    public string Name { get; set; }

    public bool PolicyValue { get; set; }
}

// AutoMapper mapping
CreateMap<DbModel, Model>(AutoMapper.MemberList.Destination)
  .ForMember(x => x.Name, opt => opt.MapFrom(src => src.DbName))
  .ForMember(x => x.PolicyValue, opt => opt.MapFromPolicyBasedOnDestination("test"))

In the example above the destination model will be used, in ths case Model.

Usage

There are two main ways to use the AutoMapper support:

  • Map in memory - Run authorization against an object in memory.

  • ProjectTo - Create an expression tree to evaluate policies with an IQueryable.

Map in memory

Mapping in memory is a useful case if you have few objects that should be evaluated and mapped. But it runs a partial evaluation against every object which can cause a delay if there are many objects.

Example:

// AutoMapper mapping
CreateMap<DbModel, Model>(AutoMapper.MemberList.Destination)
  .ForMember(x => x.Name, opt => opt.MapFrom(src => src.DbName))
  .ForMember(x => x.PolicyValue, opt => opt.MapFromPolicyBasedOnDestination("test"))

// Usage
var mappedModel = mapper.Map<Model>(databaseModel);

The property PolicyValue will now contain the result if the user passed the policy “test” on that specific object or not.

ProjectTo

ProjectTo is an AutoMapper functionality that allows the mappings to go into a database query or similar to do as much of the work as possible in the database. This is also possible with the AutoMapper support in OPADotNet. This allows the policies to be evaluated in in the database query.

This usecase is useful if one wants to evaluate alot of objects at once. For instance allowing the user to pass in a filter to only see objects that they can edit.

Example:

// AutoMapper mapping
CreateMap<DbModel, Model>(AutoMapper.MemberList.Destination)
  .ForMember(x => x.Name, opt => opt.MapFrom(src => src.DbName))
  .ForMember(x => x.PolicyValue, opt => opt.MapFromPolicyBasedOnDestination("test"))

// Usage
var mappedQueryable = mapper.ProjectTo<Model>(dbContext.Data);