1

Working with .net core 2.1 C# 7.1.

I have an API REST server.

I have the following class:

public class ActionTimingAttribute : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        // do something before the action executes
        DateTime execStart = DateTime.UtcNow;

        var request = context.HttpContext.Request;
        string payload = ""; // HERE I NEED THE PAYLOAD WITHOUT SENSITIVE DATA

        //doing funky stuff

        await next();

        DateTime execEnd = DateTime.UtcNow;
        double elapsed = (execEnd - execStart).TotalMilliseconds;

        Log(payload, elapsed);
    }
}

Now, I can take the payload with new StreamReader(request.Body) and the rest.. BUT, if I have a password field for example, I dont want the payload to have this value. I want to change the value to '#####' for example..

I have this for a lot of requests and different requests. For each POST request, I have a class representing the received json...

Obviously I dont care about HttpGet.

A method in my server looks likes this (an example just for login..)

[HttpPost, Route("Auth/Login")]
public async Task<Responses.Login> Login (Requests.Login request)

I assume I need to have some sort of attribute so it would look like:

public class Login
{
    public string Email {set;get;}

    [SensitiveData]
    public string Password {set;get;}

}

But here, I cannot make the final combination where I have the payload and I want to omit any sensitiveData.

If I had the request model, I could look on its fields and check if they are sensitive, and if so to change value in the model (assuming its wont hurt the model the controller actually receives.)

Thanks.

Ori Refael
  • 2,888
  • 3
  • 37
  • 68

1 Answers1

4

Here is a solution using custom NewtonSoft serialization. The reflection code might have some room for improvement.

public class SensitiveDataAttribute: Attribute
{
}

public sealed class SensitiveDataJsonConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        foreach (PropertyInfo prop in value.GetType().GetProperties())
        {
            object[] attrs = prop.GetCustomAttributes(true);
            if (attrs.Any(x => x is SensitiveDataAttribute))
                prop.SetValue(value, "#####");
        }

        var t = JToken.FromObject(value);
        if (t.Type != JTokenType.Object)
        {
            t.WriteTo(writer);
        }
        else
        {
            JObject o = (JObject)t;
            o.WriteTo(writer);
        }
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
    {
        throw new NotImplementedException("Unnecessary because CanRead is false. The type will skip the converter.");
    }

    public override bool CanRead => false;

    public override bool CanConvert(Type objectType)
    {
        return true;
    }
}

This is how it is used.

public class MyObject
{
    [SensitiveData]
    public string Password { get; set; }

    public string UserName { get; set; }
}

Inside ActionTimingAttribute : IAsyncActionFilter

 private static readonly SensitiveDataJsonConverter SensitiveDataConverter = new SensitiveDataJsonConverter();

 public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
 {

     string safePayload;
     if (context.ActionArguments.Count == 1)
     {
         var safeObject = context.ActionArguments[context.ActionArguments.Keys.ElementAt(0)];
          safePayload = JsonConvert.SerializeObject(safeObject, Formatting.None, SensitiveDataConverter);
     }
     else
         safePayload = request.StoreAndGetPayload();



 // the rest..
}

To handle not changing the original you can clone like described here: Deep cloning objects

public static class ObjectCopier
{
    public static T CloneJson<T>(this T source)
    {
        // Don't serialize a null object, simply return the default for that object
        if (Object.ReferenceEquals(source, null))
        {
            return default(T);
        }

        // initialize inner objects individually
        // for example in default constructor some list property initialized with some values,
        // but in 'source' these items are cleaned -
        // without ObjectCreationHandling.Replace default constructor values will be added to result
        var deserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };

        return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
    }
}

Your usage would then look like this:

safePayload = JsonConvert.SerializeObject(safeObject.CloneJson(), Formatting.None, SensitiveDataConverter);
Stephen McDowell
  • 839
  • 9
  • 21