ASP.NET WebAPI Conditional Serialization based on User Role
Asked Answered
B

3

11

I have an ORM (NHibernate) that maps to POCOs which will be returned in ApiControllers. I realize that JSON.NET allows me to put conditional serialization methods (ShouldSerialize*) on my models; however, these models and their methods have no knowledge of anything about their environment, nor should they. What I would like to do is conditionally serialize a model or one or more of its properties based on the user's role when they're signed into my website. I can conceptually perceive how this can be done but I'm lost at one part. Here's a sample model:

public class SomeModel
{
    public string SomeProperty { get; set; }

    [Sensitive]
    public string SomeOtherProperty { get; set; }
}

I would like to be able to put an attribute on a property to flag it as "Sensitive". Then in my WebApi when it is serializing it for output, I would like for it to check the model for this attribute and, if it exists, it should check the user's role. If the user is in the specified role, then the serializer should serialize the attribute, otherwise it will either mask it out or simply not serialize it. So would I have to write my own custom formatter to handle this or is there a way to hook into the built-in ones to perform this check? Or am I too limited in my thinking, and there's another way to handle this?

I did think that another way this could be handled would be at the ORM level but couldn't find good examples online.

Much appreciated!

EDIT: I did find another similar question here: Contextual serialization from WebApi endpoint based on permissions but there was no solution. Also, I don't like the idea of setting role-based access in the models via attributes. I believe that should be handled in the web application.

Basile answered 3/7, 2013 at 15:11 Comment(0)
M
9

As mentioned in the question I had raised the question "Contextual serialization from WebApi endpoint based on permissions" which I have answered myself. I did initially look at using a MediaFormatter but I believe this would then restrict you to what kind of response you could return. If you wanted to return JSON and XML you would need to implement two formatters. To only have to implement the filter in one place you need to go higher up the stack to theDelegatingHandler.

In my implementation I wanted to look up which fields the client had access to and remove any from the response that the client should not see. This is quite similar to what you want to do.

In your senario you would need to reflect over the object and pick out any fields that contain your attribute and set those values to null. If returning JSON or XML then the properties are not included in the response if the values are null so you will not even leak the property name.

Example

Here is an example implementation of a DelegatingHandler that uses reflection to filter out properties on the response object content that implement the Sensitive attribute. It assumes the object hierarchy is flat so if you have nested objects you will need to navigate the object graph and do the same for each object in the hierarchy.

public class ResponseDataFilterHandler : DelegatingHandler
{
    protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return base.SendAsync(request, cancellationToken)
            .ContinueWith(task =>
            {
                var response = task.Result;

                //Manipulate content here
                var content = response.Content as ObjectContent;
                if (content != null && content.Value != null)
                {
                    FilterFields(content.Value);
                }

                return response;
            });
    }

    private void FilterFields(object objectToFilter)
    {
        var properties = objectToFilter
                             .GetType()
                             .GetProperties(
                                 BindingFlags.IgnoreCase |
                                 BindingFlags.GetProperty |
                                 BindingFlags.Instance |
                                 BindingFlags.Public);

        foreach (var propertyInfo in properties)
        {
            if (propertyInfo.GetCustomAttributes(typeof(SensitiveAttribute), true).Any())
            {
                propertyInfo.SetValue(objectToFilter, null, new object[0]);
            }
        }   
    }
}
Millimeter answered 5/7, 2013 at 9:27 Comment(0)
B
0

A proposed solution that I came up with is to have my ApiController inherit from a BaseApiController which overrides the initalize function to set the appropriate formatter based on the user's role:

protected override void Initialize(System.Web.Http.Controllers.HttpControllerContext controllerContext)
{
    base.Initialize(controllerContext);
    // If the user is in a sensitive-data access role
    controllerContext.Configuration.Formatters.Add(/*My Formatter*/);
    // Otherwise use the default ones added in global app_start that defaults to remove sensitive data
}
Basile answered 3/7, 2013 at 15:57 Comment(0)
C
0

You could use the conditional serialization that is built into Json.Net. Here is an article describing it.

http://blog.mariusschulz.com/2013/04/15/conditionally-serializing-fields-and-properties-with-jsonnet

Perhaps the 'ShouldSerialize...' methods could check the current principal, from which you can check its roles ?

Cortico answered 25/11, 2013 at 12:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.