Permission Decorators
A follow-up to one of my previous posts: Organizer Server Permissions System.
I recently had a requirement to create permission decorators for use in our REST APIs. There had to be separate decorators for Event and Services.
Event Permission Decorators
Understanding Event permissions is simple: Any user can create an event. But access to an event is restricted to users that have Event specific Roles (e.g. Organizer, Co-organizer, etc) for that event. The creator of an event is its Organizer, so he immediately gets access to that event. You can read about these roles in the aforementioned post.
So for Events, create operation does not require any permissions, but read/update/delete operations needed a decorator. This decorator would restrict access to users with event roles.
def can_access(func):
"""Check if User can Read/Update/Delete an Event.
This is done by checking if the User has a Role in an Event.
"""
@wraps(func)
def wrapper(*args, **kwargs):
user = UserModel.query.get(login.current_user.id)
event_id = kwargs.get('event_id')
if not event_id:
raise ServerError()
# Check if event exists
get_object_or_404(EventModel, event_id)
if user.has_role(event_id):
return func(*args, **kwargs)
else:
raise PermissionDeniedError()
return wrapper
The has_role(event_id)
method of the User
class determines if the user has a Role in an event.
# User Model class
def has_role(self, event_id):
"""Checks if user has any of the Roles at an Event.
"""
uer = UsersEventsRoles.query.filter_by(user=self, event_id=event_id).first()
if uer is None:
return False
else:
return True
Reading one particular event (/events/:id [GET]
) can be restricted to users, but a GET request to fetch all the events (/events [GET]
) should only be available to staff (Admin and Super Admin). So a separate decorator to restrict access to Staff members was needed.
def staff_only(func):
@wraps(func)
def wrapper(*args, **kwargs):
user = UserModel.query.get(login.current_user.id)
if user.is_staff:
return func(*args, **kwargs)
else:
raise PermissionDeniedError()
return wrapper
Service Permission Decorators
Service Permissions for a user are defined using Event Roles. What Role a user has in an Event determines what Services he has access to in that Event. Access here means permission to Create, Read, Update and Delete services. The User model class has four methods to determine the permissions for a Service in an event.
user.can_create(service, event_id)
user.can_read(service, event_id)
user.can_update(service, event_id)
user.can_delete(service, event_id)
So four decorators were needed to put alongside POST, GET, PUT and DELETE method handlers. I’ve pasted snippet for the can_update
decorator. The rest are similar but with their respective permission methods for User class object.
def can_update(DAO):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
user = UserModel.query.get(login.current_user.id)
event_id = kwargs.get('event_id')
if not event_id:
raise ServerError()
# Check if event exists
get_object_or_404(EventModel, event_id)
service_class = DAO.model
if user.can_update(service_class, event_id):
return func(*args, **kwargs)
else:
raise PermissionDeniedError()
return wrapper
return decorator
This decorator is a little different than can_access
event decorator in a way that it takes an argument, DAO
. DAO is Data Access Object. A DAO includes a database Model and methods to create, read, update and delete object of that model. The db model for a DAO would be the Service class for the object. You can look that the model class is taken from the DAO and used as the service class.
The can_create
, can_read
and can_delete
decorators look exactly the same except they use their (obvious) permission methods on the User class object.
You must be logged in to post a comment.