Implemeting Permissions for Speakers API in Open Event API Server
In my previous blogpost I talked about what the permissions enlisted in developer handbook means and which part of the codebase defines what part of the permissions clauses. The permission manager provides the permissions framework to implement the permissions and proper access controls based on the dev handbook.
In this blogpost, the actual implementation of the permissions is described. (Speakers API is under consideration here). The following table is the permissions in the developer handbook.
List |
View |
Create |
Update |
Delete |
|
Superadmin/admin |
✓ |
✓ |
✓ |
✓ |
✓ |
Event organizer |
✓ [1] |
✓ [1] |
✓ [1] |
✓ [1] |
✓ [1] |
Registered User |
✓ [3] |
✓ [3] |
✓ [4] |
✓ [3] |
✓ [3] |
Everyone else |
✓ [2][4] |
✓ [2][4] |
- Only self-owned events
- Only of sessions with state approved or accepted
- Only of self-submitted sessions
- Only to events with state published.
Super admin and admin should be able to access all the methods – list, view, create, update and delete. All the permissions are implemented through functions derived from permissions manager.Since all the functions have first check for super admin and admin, these are automatically taken care of.
Only of self-submitted sessions
This means that a registered user can list, view, edit or delete speakers of a session which he himself submitted. This requires adding a ‘creator’ attribute to session object which will help us determine if the session was created by the user. So before making a post for sessions, the current user identity is included as part of the payload.
def before_post(self, args, kwargs, data): data['creator_id'] = current_identity.id
Now that we have added creator id to a session, a method is used to check if session was created by the same user.
def is_session_self_submitted(view, view_args, view_kwargs, *args, **kwargs): user = current_identity
Firstly the current identity is set as user which will later be used to check id. Sequentially, admin, superadmin, organizer and co-organizers are checked. After this a session is fetched using kwargs[session_id]. Then if the current user id is same as the creator id of the session fetched, access is granted, else Forbidden Error is returned.
if session.creator_id == user.id: return view(*view_args, **view_kwargs)
In the before_post method of speakers class, the session ids received in the data are passed to this function in kwargs as session_id. The permissions are then checked there using current user. If the session id are not those of self submitted sessions, ‘Session Not Found’ is returned.
if not has_access('is_session_self_submitted', session_id=session_id): raise ObjectNotFound({'parameter': 'session_id'}, "Session: {} not found".format(session_id))
Only of sessions with state approved or accepted
This check is required for user who has not submitted the session himself, so he can only see speaker profiles of accepted sessions. First, if the user is not authenticated, permissions are not checked. If co-organizer access is available, then the user can see all the speakers, so for this case filtering is not done. If not, then ‘is_session_self_submitted’ is checked. If yes, then then again no filtering, but if not then the following query filters accepted sessions.
if not has_access('is_session_self_submitted', session_id=session.id): query_ = query_.filter(Session.state == "approved" or Session.state == "accepted")
Similarly all the permissions first generate a list of all objects and then filtering is done based on the access level, instead of getting the list based on permissions.
Only to events with state published
It is necessary that users except the organizers and co-organizers can not see the events which are in draft state. The same thing follows for speaker profiles – a user cannot submit or view a speaker profile to an unpublished event. Hence, this constraint. So before POST of speakers, if event is not published, an event not found error is returned.
if event.state == "draft": raise ObjectNotFound({'parameter': 'event_id'}, "Event: {} not found".format(data['event_id'])
For GET, the implementation of this is similar to the previous permission. A basic query is generated as such:
query_ = query_.join(Event).filter(Event.id == event.id)
Now if the user does not have at least co-organizer access, draft events must be filtered out.
if not has_access('is_coorganizer', event_id=event.id): query_ = query_.filter(Event.state == "published")
Some of the finer details have been skipped here, which can be found in the code.
Resources
- The Open Event Developer Handbook – Niranjan R
- Permissions and Data layer | Flask-REST-JSONAPI
- REST APIs with Python and Flask – Miguel Grinberg [blog]
- Access Control with Postgres + Flask – Christian Jauvin [blog]