JSONAPIdotNET / JSONAPI.NET

MIT License
102 stars 23 forks source link

Create an authorization framework #35

Open SphtKr opened 9 years ago

SphtKr commented 9 years ago

This is something I had attempted the oft-mentioned original version of this library, but it ended up very very cumbersome and error prone--I'm not sure this will be practical

The "Dream" of JSONAPI.EntityFramework.Http.ApiController and really of JSONAPI.Http.ApiController and IMaterializer is to be able to provide an almost completely canned JSON API compliant service broker--all you have to do is to override/implement a few interface points to integrate with your persistence layer and you're done. However, especially in the case of the EF ApiController, this has turned out to be more pipe dream so far, as I usually have to override the verb methods and it's hard to reuse the super-method.

One of the main reasons I find myself having to throw away the default verb implementations (particularly Put, Post, and Delete is to insert authorization controls, for example to make sure users can only edit their own records. Additionally, I often need to control things at the property level, e.g. a user can't change the Owner of a record to someone else (but an admin user may be able to do this).

In the current architecture, the separation of the deserialization from the IMaterializer provides a good place to inject a reusable authorization layer. We could add an IAuthorizationProvider property to the IMaterializer, and have some API for the IMaterializer to ask the IAuthorizationProvider whether any given property update should be allowed. This in theory would be good from a separation of concerns perspective, because it would encourage encapsulation of security logic from other business logic.

Gotchas (from previous experience):

csantero commented 9 years ago

Ah yes, authorization. I haven't tackled this on my project yet, but my requirements are something as follows:

  1. If a user only has access to some of the fields on a resource, they should only see the fields they have read-access to.
  2. If they have access to 0 of the fields, they should get a 403 Forbidden. If they try to update fields that they don't have access to update, same.
  3. You can have access to certain fields on one resource of a given type but not have access for those fields on another resource of the same type. So I can edit the first name and last name of Person 100 but only read those fields for Person 101, and not even have read access for Person 102. This is dependent on security rules that are tied to my application logic, so not a json-api concern.

I intend to do authorization stuff in general in my service layer. Currently I have my own base ApiController that exposes Get(), Put(), et al., which merely wrap service layer methods. In order for me to be able to use ApiController<>, I need to be able to maintain this separation of concerns (i.e., IMaterializer has to be able to do everything).

So I don't know how much authorization needs to be done in this library. At least as far as my project goes, the service layer has to be able to tell the API which fields NOT to serialize on a per-request basis, and when to return a 403 entirely.

(BTW, I know the service layer could just return null for fields the user can't read. But I need some way to communicate to the client app the difference between a field that has been restricted and one that just has null data. In the latter case the client will render an empty field; in the former, the field is completely hidden.)

SphtKr commented 9 years ago

So I don't know how much authorization needs to be done in this library.

Right, I'm more getting at providing hooks where the user can specify their own authorization logic. Particularly because it would be handy within, for instance, EntityFrameworkMaterializer.Merge to catch attempted changes to properties while such changes were being made. Wherever #36 goes, similar hooks in Post and Delete methods would be handy too.

If a user only has access to some of the fields on a resource, they should only see the fields they have read-access to. ... the service layer has to be able to tell the API which fields NOT to serialize ... I need some way to communicate to the client app the difference between a field that has been restricted and one that just has null data. In the latter case the client will render an empty field; in the former, the field is completely hidden.

I'm concerned that if we build this behavior into the library that we'll butt up against support for sparse fieldsets, which is a cool way to save bandwidth and I'd like to support it. Personally I'd recommend finding another way to communicate to your client that the field was restricted (meta key in the payload? once upon a time I thought the spec had provisions for the meta key within a resource). Nevertheless, you might look into building a IContractResolver for your model objects, which I hope can be used on top of JSONAPI.NET.