Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET WebAPI Conditional Serialization based on User Role

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.

like image 457
Zach Avatar asked Jul 03 '13 15:07

Zach


1 Answers

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]);
            }
        }   
    }
}
like image 153
Bronumski Avatar answered Oct 19 '22 21:10

Bronumski