3

I have a business requirement to only send permissioned properties in our response payload. For instance, our response DTO may have several properties, and one of them is SSN. If the user doesn't have permissions to view the SSN then I would never want it to be in the Json response. The second requirement is that we send null values if the client has permissions to view or change the property. Because of the second requirement setting the properties that the user cannot view to null will not work. I have to still return null values.

I have a solution that will work. I create an expandoObject by reflecting through my DTO and add only the properties that I need. This is working in my tests.

I have looked at implementing ITextSerializer. I could use that and wrap my response DTO in another object that would have a list of properties to skip. Then I could roll my own SerializeToString() and SerializeToStream(). I don't really see any other ways at this point. I can't use the JsConfig and make a SerializeFn because the properties to skip would change with each request.

So I think that implementing ITextSerializer is a good option. Are there any good examples of this getting implemented? I would really like to use all the hard work that was already done in the serializer and take advantage of the great performance. I think that in an ideal world I would just need to add a check in the WriteType.WriteProperties() to look and the property is one to write, but that is internal and really, most of them are so I can't really take advantage of them.

If someone has some insight please let me know! Maybe I am making the implementation of ITextSerialzer a lot harder that it really is?

Thanks!

Pull request #359 added the property "ExcludePropertyReference" to the JsConfig and the JsConfigScope. You can now exclude references in scope like I needed to.

Nick H
  • 245
  • 2
  • 13

1 Answers1

0

I would be hesitant to write my own Serializer. I would try to find solutions that you can plug in into the existing ServiceStack code. That way you will have to worry less about updating dlls and breaking changes.

One potential solution would be decorating your properties with a Custom Attributes that you could reflect upon and obscure the property values. This could be done in the Service before Serialization even happens. This would still include values that they user does not have permission to see but I would argue that if you null those properties out they won't even be serialized by JSON anyways. If you keep all the properties the same they you will keep the benefits of strong typed DTOs.

Here is some hacky code I quickly came up with to demonstrate this. I would move this into a plugin and make the reflection faster with some sort of property caching but I think you will get the idea.

Hit the url twice using the following routes to see it in action.

  • /test?role
  • /test?role=Admin (hack to pretend to be an authenticated request)

[System.AttributeUsage(System.AttributeTargets.Property)]
public class SecureProperty : System.Attribute
{
    public string Role {get;set;}

    public SecureProperty(string role)
    {
        Role = role;
    }
}

[Route("/test")]
public class Test : IReturn
{
    public string Name { get; set; }

    [SecureProperty("Admin")]
    public string SSN { get; set; }

    public string SSN2 { get; set; }

    public string Role {get;set;}
}

public class TestService : Service
{
    public object Get(Test request)
    {   
        // hack to demo roles.
        var usersCurrentRole = request.Role;

        var props = typeof(Test).GetProperties()
        .Where(
            prop => ((SecureProperty[])prop
                .GetCustomAttributes(typeof(SecureProperty), false))
                .Any(att => att.Role != usersCurrentRole)
        );

        var t = new Test() {
            Name = "Joe", 
            SSN = "123-45-6789", 
            SSN2 = "123-45-6789" };

        foreach(var p in props) {
            p.SetValue(t, "xxx-xx-xxxx", null);
        }

        return t;
    }
}

Require().StartHost("http://localhost:8080/", 
    configurationBuilder: host => { });

I create this demo in ScriptCS. Check it out.

kampsj
  • 3,139
  • 5
  • 34
  • 56
  • Thank you for the help, but my whole reason for wanting to change the serializer is so that I won't reflect through the response object more than once. Your solution would still require that. I do however like your idea of creating a custom attribute. I will have some properties that are not permission end and I can decorate those with attributes. – Nick H Jul 28 '13 at 20:32