1

In an ASP.NET Web API project I want to encrypt all of entity ID in all responses and decrypt the encrypted values in all the requests.

(NOTE: I know how to encrypt/decrypt data, that is not my question.)

I think it would be nice if I only decorate the properties that I need to be encrypted/decrypted in responses/requests, with a custom attribute.

This is how I like it to work:

public class Person
{
  [EncryptDecrypt]
  public int PersonID {get; set;}

  public string Name {get; set;}

  public IEnumerable<Order> Orders {get; set;}
}

public class Order 
{
    [EncryptDecrypt]
    public long OrderID {get; set;}

    public string Title {get; set;}

    public float Price {get; set;}
}

Then in the Web API method:

// GET: api/persons/xhj$j78dPs (xhj$j78dPs is an encrypted PersonID)    

public Person Get([EncryptDecrypt]int personId)
{
    // Now, I expect personId to be a normal ID, like: 187356

    Person person = _repository.GetPerson(personId);

    return person;
}

The desire response for the above Web API is:

{
   "personId": "xhj$j78dPs",
   "name": "Joe Williams",
   "orders": [
      {
         "orderId": "a#jd75mlzed0ihd",
         "title": "Buying a new item",
         "price": 19.99
      }
    ]
 }

And this is another example, this time a Web API for PUT verb:

/* PUT Request body: */
{
   "orderId": "a#jd75mlzed0ihd",
   "title": "Buying a new item - edited",
   "price": 13.00
}

related Web API method:

// PUT: api/persons/xhj$j78dPs/orders/ (xhj$j78dPs is an encrypted PersonID)

public void Put([EncryptDecrypt]int personId, Order editedOrder)
{
    // I expect personId to be a normal ID, like: 187356

    // I expect editedOrder.OrderID to be a normal ID, like: 10000089765

    _repository.UpdateOrder(personId, editedOrder);
}

How can I develop the [EncryptDecrypt] attribute?

Is [EncryptDecrypt] should be actually a JsonConverter attribute? Or should I develop a custom Media Formatter or Model Binder or Value Provider or Parameter Binder? I am confused.

Tohid
  • 6,175
  • 7
  • 51
  • 80
  • Would something like this work? http://www.codemag.com/article/0307041 – Sherman Apr 01 '16 at 17:15
  • 2
    See [How can I encrypt selected properties when serializing my objects?](http://stackoverflow.com/q/29196809/10263) for an idea of how to handle the serialization end of it. You'd have to tailor it a bit since you want this to work on numeric properties rather than strings. For the Web API parameter handling, I think you'd want to create a custom Web API `IValueProvider` and `ValueProviderFactory`. Take a look here: http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api – Brian Rogers Apr 01 '16 at 17:54

1 Answers1

3

How can I develop the [EncryptDecrypt] attribute?

Is [EncryptDecrypt] should be actually a JsonConverter attribute? Or should I develop a custom Media Formatter or Model Binder or Value Provider or Parameter Binder? I am confused.

You need to develop a bit of both; a custom JsonConverter for (de)serializing the JSON data, and a custom ModelBinder for binding the (encrypted int/ long) value to the endpoint parameter.

Try something like this:

public class EncryptDecrypt : JsonConverter, IModelBinder 
{    
  public override bool CanConvert(Type objectType)
  {
    return typeof(int).IsAssignableFrom(objectType) || 
           typeof(long).IsAssignableFrom(objectType);
  }

  public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
  {
    // Deserialize the provided value as string
    // and decrypt it to its exprected int/long type 
    var value = serializer.Deserialize<string>(reader);
    return Decrypt(value, objectType);
  }

  public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
  {
    // obviously Encrypt() should convert the int/ long value 
    // to its encrypted string representation.
    var encrypted = Encrypt(value);
    writer.WriteValue(encrypted);
  }

  public bool BindModel(HttpActionContext actionContext, ModelBindingContext bindingContext)
  {
    if (!CanConvert(bindingContext.ModelType)) return false;

    var val = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
    if (val == null) return false;

    // bindingContext.ModelType should tell us whether the decrypted value 
    // is expected as an int/ long.
    var decrypted = Decrypt(val.RawValue as string, bindingContext.ModelType);
    if (decrypted != null)
    {
      bindingContext.Model = decrypted;
      return true;
    }

    bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Cannot convert value");
    return false;
  }
}

You could then decorate the models like this:

public class Person
{
  [JsonConverter(typeof(EncryptDecrypt))]
  public int PersonID { get; set; }

  public string Name { get; set; }

  public IEnumerable<Order> Orders { get; set; }
}

public class Order
{
  [JsonConverter(typeof(EncryptDecrypt))]    
  public long OrderID { get; set; }

  public string Title { get; set; }

  public float Price { get; set; }
}

As for the Web API methods, you'd need to decorate it like this:

public IHttpActionResult Get([ModelBinder(typeof(EncryptDecrypt))] int personId)
{
  // Now, I expect personId to be a normal ID, like: 187356
  Person person = _repository.GetPerson(personId);

  return Json(person);
}

public void Put([ModelBinder(typeof(EncryptDecrypt))] int personId, Order editedOrder)
{
  // I expect personId to be a normal ID, like: 187356
  // I expect editedOrder.OrderID to be a normal ID, like: 10000089765

  _repository.UpdateOrder(personId, editedOrder);
}
Community
  • 1
  • 1
IronGeek
  • 4,764
  • 25
  • 27