Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RESTfully routing an API based on user roles

I am developing an API using Flask-RESTful, and my application has three roles.

  1. site_admin
  2. department_admin
  3. basic

For any given resource, the JSON object returned has a different set of keys based on each role.

For example, if you hit /orders as "site_admin", the result could look like this:

{
  "orders": [
    {"id": 1, "user": "foo", "paid": True,  "department": "A", "code": 456},
    {"id": 2, "user": "bar", "paid": False, "department": "A", "code": 567},
    {"id": 3, "user": "meh", "paid": False, "department": "B", "code": 678}
  ]
}

However, if you hit /orders as "department_admin", the result could look like this:

{
  "orders": [
    {"id": 3, "user": "meh", "paid": False}
  ]
}

And if you hit /orders as "basic" it would be a very minimal JSON response like this:

{
  "orders": [
    {"id": 2, "paid": True}
  ]
}

What is the RESTful way of implementing this?

I can come up with three ways of doing it.

(1) using a request arg and filtering on that:

class Orders(restful.Resource):
  def get(self):
    if request.args['role'] == 'site_admin':
      return admin_JSON_response()
    elif request.args['role'] == 'department_admin':
      return dept_admin_JSON_response()
    else:
      return basic_JSON_response()

api.add_resource(Orders, '/orders')

(2) filtering on the session object:

class Orders(restful.Resource):
  def get(self):
    if session['role'] == 'site_admin':
      return admin_JSON_response()
    elif session['role'] == 'department_admin':
      return dept_admin_JSON_response()
    else:
      return basic_JSON_response()

api.add_resource(Orders, '/orders')

(3) having a different route for each role:

class OrdersSiteAdmin(restful.Resource):
  def get(self):
    return admin_JSON_response()
api.add_resource(OrdersSiteAdmin, '/orders_site_admin')

class OrdersDeptAdmin(restful.Resource):
  def get(self):
    return dept_admin_JSON_response()
api.add_resource(OrdersDeptAdmin, '/orders_dept_admin')

class OrdersBasic(restful.Resource):
  def get(self):
      return basic_JSON_response()
api.add_resource(OrdersBasic, '/orders_basic')

... Is there a consensus on which is the preferred way RESTfully?

Thanks so much!

like image 491
SeanPlusPlus Avatar asked Dec 01 '14 23:12

SeanPlusPlus


1 Answers

Your option #2 violates the the "stateless" constraint, the use of user sessions is not a good idea in a REST API, and instead you should require your clients to provide authentication with every request.

Let's assume you fix #2 and instead of a user session you now have a current_user variable, which is populated during authentication. Then you could rewrite that example as follows:

class Orders(restful.Resource):
  def get(self):
    if current_user.role == 'site_admin':
      return admin_JSON_response()
    elif current_user.role == 'department_admin':
      return dept_admin_JSON_response()
    else:
      return basic_JSON_response()

api.add_resource(Orders, '/orders')

Let's look at your three options one by one:

  • (1) specifies the role in the query string, which would enable any user to request any representation, just by passing the desired role. But why put the role in the query string? I assume you will authenticate your users, so knowing your user you also know the role. This seems unnecessary and will give you extra validation effort.

  • (3) creates different resources for each role. Once again, you have to ensure that a "basic" user does not have access to the two URLs that apply to the higher roles, so you have also some validation work here.

  • (2) assumes the user database stores the role of each user, so once the user is authenticated the correct representation for his/her role is returned based on the assigned role. This is, I think, the best option, as users have really no way to hack their way into data they are not allowed to see.

Speaking of being RESTful, I would also look at your representations, which can be improved. Consider implementing links to other resources instead of providing IDs, to comply with the HATEOAS constraint.

like image 163
Miguel Avatar answered Sep 23 '22 16:09

Miguel