2
\$\begingroup\$

I have this method (consisting of three methods) that I need to use for looking through objects that are going to be serializes and logged as json objects.

a bit of intro (not necessary to read)

The initial problem was that I noticed that sometimes I would receive data in objects from external api's that would contain personal sensitive data i.e social security numbers. I would still like to log these objets though, I just want to sanitize the social sec numbers away, and I also would like to have some method that could this for object with any kind of internal structure, such that I could reuse it, and would'nt need to rewrite a new sanitization method for each log.

The problem

So now I wanted these methods that could look into objects of any type, and sanitize all the string fields, such that the strings would look the same, but for any string field with a social security number of the type 123456-7891, should be replaced with XXXXXX-XXXX.

public static void DoitNow(object obj)
{
    Queue<PropertyInfo> properties = new Queue<PropertyInfo>(obj.GetType().GetProperties());
    while (properties.Count != 0)
    {
        PropertyInfo property = properties.Dequeue();

        if (property.GetValue(obj) is string)
        {
            string stringField = property.GetValue(obj).ToString();

            String sanitizedField = sanitizeSensitiveString(stringField);
            property.SetValue(obj, sanitizedField);

        }
        else if (property.GetValue(obj) is IList)
        {
            IList y = (IList)property.GetValue(obj);

            if (y.Count != 0)
            {
                if (y[0] is string)
                {
                    for (int i = 0; i < y.Count; i++)
                        y[i] = sanitizeSensitiveString((string)y[i]);
                }
                else if (IsNested(y[0]))
                {
                    for (int i = 0; i < y.Count; i++)
                    {
                        DoitNow(y[i]);
                    }
                }
            }
        }
        else if (IsNested(property.GetValue(obj)))
        {
            var l = property.GetValue(obj);

            DoitNow(l);
        }
    }
}
    
public static string sanitizeSensitiveString(string sensitiveString)
{
    return Regex.Replace(sensitiveString, @"\d{6}-?\d{4}[0-9]*", " XXXXXX-XXXX ");
}

public static bool IsNested(object value)
{    
    Type t = value.GetType();
    if (t.IsPrimitive || value is string)
    {
        return false;
    }
    FieldInfo[] fields = t.GetFields(BindingFlags.NonPublic |
                                     BindingFlags.Public |
                                     BindingFlags.Instance);
    if (fields.Any())
    {
        return true;
    }
    return false;
}

Now, the method DoitNow is the core method here, it takes any object as a parameter, and uses projection to look through all the properties of it. If a field is a string field, we use the method sanitizeSensitiveString to sanitize the string. If a property is itself a object (I use the method isNested to determine this) I will call the method recursively on it. If a property is a List type, I look into the list and sanitize if it contains strings, and recurse if it contains complex objects.

I would like to get some tips on my IsNested method, am I using projection right? In general am I using projection In a way that makes sense to access the fields and modify them?

Also, In order to be able to operate with many list types, I use the IList interface, however this has led me to using for-loops in a way that I find very un-elegant, can I replace some of my loops with some functional type function calls? I also use if (list.count != 0) to test if a list is empty, I just find this plain stupid.

Any other optimizations are welcomed!

\$\endgroup\$
4
  • \$\begingroup\$ Your method is called DoitNow? Your parameter is named obj? \$\endgroup\$ – BCdotWEB Dec 17 '20 at 10:25
  • \$\begingroup\$ yeah Doitnow, but obj makes good sense, nice and generic \$\endgroup\$ – n00bster Dec 17 '20 at 10:28
  • \$\begingroup\$ @n00bster Your regex accepts this input as well: 1234567891 so without the hyphen. \$\endgroup\$ – Peter Csala Dec 17 '20 at 11:54
  • \$\begingroup\$ yes this is on purpose \$\endgroup\$ – n00bster Dec 17 '20 at 12:23
2
\$\begingroup\$

As always simplicity is king. In my opinion you have done a lots of extra effort which might not be necessary.

Let's look at the problem from 10 000 feet high:

  • You receive an arbitrary object as input
  • You need to produce a string output where the SN data are masked out

What if you first serialize the data and then mask it?

public static string MaskOut(object toBeMaskedObject,
    string regexPattern = @"\d{6}-?\d{4}[0-9]*",
    string mask = " XXXXXX-XXXX ")
{
    var serializedData = JsonConvert.SerializeObject(toBeMaskedObject);
    return Regex.Replace(serializedData, regexPattern, mask);
}

And that's it.

I've used the following data structures for testing:

var simpleWithoutSN = new
{
    Desc = "123",
    Id = 1
};

var simpleWithSN = new
{
    SN = "123456-7891",
    Id = 1 
};

var nestedWithSN = new
{
    Id = 1,
    Items = new []
    {
        new { SN  = "123456-7891",},
        new { SN  = "123456-7892",},
    }
    
};

Console.WriteLine(MaskOut(simpleWithoutSN));
Console.WriteLine(MaskOut(simpleWithSN));
Console.WriteLine(MaskOut(nestedWithSN));

The output will be:

{"Desc":"123","Id":1}
{"SN":" XXXXXX-XXXX ","Id":1}
{"Id":1,"Items":[{"SN":" XXXXXX-XXXX "},{"SN":" XXXXXX-XXXX "}]}
\$\endgroup\$
1
  • \$\begingroup\$ This might work! The only thing is that the logging framework I am required to use already does some serializing to json, which I think might create issues, if I attempt to serialize a json object to json \$\endgroup\$ – n00bster Dec 17 '20 at 12:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.