Implementing Permissions for Orders API in Open Event API Server

Open Event API Server Orders API is one of the core APIs. The permissions in Orders API are robust and secure enough to ensure no leak on payment and ticketing.The permission manager provides the permissions framework to implement the permissions and proper access controls based on the dev handbook. The following table is the permissions in the developer handbook.   List View Create Update Delete Superadmin/admin ✓ ✓ ✓ ✓ ✓ Event organizer ✓ [1] ✓ [1] ✓ [1] ✓ [1][2] ✓ [1][3] Registered user ✓ [4] Everyone else Only self-owned events Can only change order status A refund will also be initiated if paid ticket Only if order placed by self Super Admins and admins are allowed to create any order with any amount but any coupon they apply is not consumed on creating order. They can update almost every field of the order and can provide any custom status to the order. Permissions are applied with the help of Permission Manager which takes care the authorization roles. For example, if a permission is set based on admin access then it is automatically set for super admin as well i.e., to the people with higher rank. Self-owned events This allows the event admins, Organizer and Co-Organizer to manage the orders of the event they own. This allows then to view all orders and create orders with or without discount coupon with any custom price and update status of orders. Event admins can provide specific status while others cannot if not has_access('is_coorganizer', event_id=data['event']): data['status'] = 'pending' And Listing requires Co-Organizer access elif not has_access('is_coorganizer', event_id=kwargs['event_id']): raise ForbiddenException({'source': ''}, "Co-Organizer Access Required") Can only change order status The organizer cannot change the order fields except the status of the order. Only Server Admin and Super Admins are allowed to update any field of the order. if not has_access('is_admin'): for element in data: if element != 'status': setattr(data, element, getattr(order, element)) And Delete access is prohibited to event admins thus only Server admins can delete orders by providing a cancelling note which will be provided to the Attendee/Buyer. def before_delete_object(self, order, view_kwargs): if not has_access('is_coorganizer', event_id=order.event.id): raise ForbiddenException({'source': ''}, 'Access Forbidden') Registered User A registered user can create order with basic details like the attendees' records and payment method with fields like country and city. They are not allowed to provide any custom status to the order they are creating. All orders will be set by default to “pending” Also, they are not allowed to update any field in their order. Any status update will be done internally thus maintaining the security of Order System. Although they are allowed to view their place orders. This is done by comparing their logged in user id with the user id of the purchaser. if not has_access('is_coorganizer_or_user_itself', event_id=order.event_id, user_id=order.user_id): return ForbiddenException({'source': ''}, 'Access Forbidden') Event Admins The event admins have one more restriction, as an event admin, you cannot provide discount coupon and even if you do it will be ignored. # Apply discount only…

Continue ReadingImplementing Permissions for Orders API in Open Event API Server

Understanding Permissions for Various APIs in Open Event API Server

Since the Open Event Server has various elements, a proper permissions system is essential. This huge list of permissions is well compiled in the developer handbook which can be found here. In this blogpost, permissions listed in the developer handbook are discussed. Let’s start with what we wish to achieve, that is, how to make sense of these permissions and where does each clause fit in the API Server’s codebase. For example, Sponsors API has the following permissions. 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 sessions with state approved or accepted Only self-submitted sessions Only to events with state published. Based on flask-rest-jsonapi resource manager, we get list create under ResourceList through ResourceList’s GET and POST methods, whereas View, Update, Delete work on single objects and hence are provided by ResourceDetail’s GET, PATCH and DELETE respectively. Each function of the permission manager has a jwt_required decorator. @jwt_required def is_super_admin(view, view_args, view_kwargs, *args, **kwargs): @jwt_required def is_session_self_submitted(view, view_args, view_kwargs, *args, **kwargs): This ensures that whenever a check for access control is made to the permission manager, the user is signed in to Open Event. Additionally, the permissions are written in a hierarchical way such that for every permission, first the useris checked for admin or super admin, then for other accesses. Similar hierarchy is kept for organizer accesses like track organizer, registrar, staff or organizer and co-organizer. Some APIs resources require no authentication for List. To do this we need to add a check for Authentication token in the headers. Since each of the functions of permission manager have jwt_required as decorator, it is important to checkfor the presence of JWT token in request headers, because we can proceed to check for specific permissions in that case only. if 'Authorization' in request.headers: _jwt_required(current_app.config['JWT_DEFAULT_REALM']) Since the resources are created by endpoints of the form : ‘/v1/<resource>/` , this is derived from the separate ResourceListPost class. This class is POST only and has a before_create object method where the required relationships and permissions are checked before inserting the data in the tables. In the before_create method, let’s say that event is a required relationship, which will be defined by the ResourceRelationRequired , then we use our custom method def require_relationship(resource_list, data): for resource in resource_list: if resource not in data: raise UnprocessableEntity({'pointer': '/data/relationships/{}'.format(resource)}, "A valid relationship with {} resource is required".format(resource)) to check if the required relationships are present in the data. The event_id here can also be used to check for organizer or co-organizer access in the permissions manager for a particular event. Here’s another permissions structure for a different API - Settings. List View Create Update Delete Superadmin/admin ✓ ✓ Everyone else ✓ [1] Only app_name, tagline, analytics_key, stripe_publishable_key, google_url, github_url, twitter_url, support_url, facebook_url, youtube_url, android_app_url, web_app_url fields . This API does not allow access to the complete object, but to only some fields which are listed above. The complete details can be checked here. Resources The Open Event Developer Handbook - Niranjan R Resource Manager | Flask-REST-JSONAPI Permissions | Flask-REST-JSONAPI REST APIs with Python and Flask - Miguel Grinberg [blog] User Roles in Flask - Safari, O'Reilly Media [blog]

Continue ReadingUnderstanding Permissions for Various APIs in Open Event API Server

A guide to use Permission Manager in Open Event API Server

This article provides a simple guide to use permission manager in Open Event API Server. Permission manager is constantly being improved and new features are being added into it. To ensure that all co-developers get to know about it and make use of them, this blog posts describes every part of permission manager. Bootstrapping Permission manager as a part of flask-rest-jsonapi works as a decorator for different resources of the API. There are two ways to provide the permission decorator to any view First one is to provide it in the list of decorators decorators = (api.has_permission('is_coorganizer', fetch="event_id", fetch_as="event_id", model=StripeAuthorization),) Second way is to explicitly provide it as a decorator to any view @api.has_permission('custom_arg', custom_kwargs='custom_kwargs') def get(*args, **kwargs): return 'Hello world !' In the process of booting up, we first need to understand the flow of Resources in API. All resources even before doing any schema check, call the decorators. So this way you will not get any request data in the permission methods. All you will receive is a dict of the URL parameters but again it will not include the filter parameters. Permission Manager receives five parameters as:  def permission_manager(view, view_args, view_kwargs, *args, **kwargs): First three are provided into it implicitly by flask-rest-jsonapi module view: This is the resource’s view method which is called through the API. For example, if I go to /events then the get method of ResourceList will be called. view_args: These are args associated with that view. view_kwargs: These are kwargs associated with that resource view. It includes all your URL parameters as well. args: These are the custom args which are provided when calling the permission manager. Here at permission manager is it expected that the first index of args will be the name of permission to check for. kwargs: This is the custom dict which is provided on calling the permission manager. The main pillar of the permission manager. Described below in usage. Using Permission Manager Using permission manager is basically understanding the different options you can send through the kwargs so here is the list of the things you can send to permission manager These are all described in the order of priority in permission manager method (string): You can provide a string containing the methods where permission needs to be checked as comma separated values of different methods in a string. For example: method=”GET,POST” leave_if (lambda): This receives a lambda function which should return boolean values. Based on returned value if is true then it will skip the permission check. The provided lambda function receives only parameter, “view_kwargs” Example use case can be the situation where you can leave the permission for any specifically related endpoint to some resource and would like to do a manual check in the method itself. check (lambda): Opposite to leave_if. It receives a lambda function that will return boolean values. Based on returned value, If it is true then only it will go further and check the request for permissions else will throw forbidden…

Continue ReadingA guide to use Permission Manager in Open Event API Server

Permission Manager in Open Event API Server

Open Event API Server uses different decorators to control permissions for different access levels as discussed here. Next challenging thing for permissions was reducing redundancy and ensuring permission decorators are independent of different API views. They should not look to the view for which they are checking the permission or some different logic for different views. In API Server, we have different endpoints that leads to same Resource this way we maintain relationships between different entities but this leads to a problem where permission decorators has to work on different API endpoints that points to different or same resource and but to check a permission some attributes are required and one or more endpoints may not provide all attributes required to check a permission. For instance, PATCH /session/id` request requires permissions of a Co-Organizer and permission decorator for this requires two things, user detail and event details. It is easy to fetch user_id from logged in user while it was challenging to get “event_id”. Therefore to solve this purpose I worked on a module named “permission_manager.py” situated at “app/api/helpers/permission_manager.py” in the codebase Basic Idea of Permission Manager Permission manager basically works to serve the required attributes/view_kwargs to permission decorators so that these decorators do not break Its logic can be described as: It first sits in the middle of a request and permission decorator Evaluates the arguments passed to it and ensure the current method of the request (POST, GET, etc ) is the part of permission check or not. Uses two important things, fetch and fetch_as fetch => value of this argument is the URL parameter key which will be fetched from URL or the database ( if not present in URL ) fetch_as => the value received from fetch will be sent to permission decorator by the name as the value of this option. If the fetch key is not there in URL, It uses third parameter model which is Model if the table from where this key can be fetched and then passes it to permission decorator Returns the requested view on passing access level and Forbidden error if fails This way it ensures that if looks for the only specific type of requests allowing us to set different rules for different methods. if 'methods' in kwargs: methods = kwargs['methods'] if request.method not in methods: return view(*view_args, **view_kwargs) Implementing Permission Manager Implementing it was a simple thing, Firstly, registration of JSON API app is shifted from app/api/__init__.py to app/api/bootstrap.py so that this module can be imported anywhere Added permission manager to the app_v1 module Created permission_manager.py in app/api/helpers Added it’s usage in different APIs An example Usage: decorators = (api.has_permission('is_coorganizer', fetch='event_id', fetch_as="event_id", methods="POST", check=lambda a: a.get('event_id') or a.get('event_identifier')),) Here we are checking if the request has the permission of a Co-Organizer and for this, we need to fetch event_id  from request URI. Since no model is provided here so it is required for event_id in URL this also ensures no other endpoints can leak the resource. Also…

Continue ReadingPermission Manager in Open Event API Server

Permission Dependent Schema for Admin Settings in Open Event Server

For implementing the next version of the API in Open Event, the schema is a very important thing. It tells you exactly what all information you need to send in the body and how the response will look. In flask-rest-jsonapi, we usually mention a schema for an API which is then used for validating requests and sending response. Using decorators, we restrict who all can create, edit or get responses from a particular API endpoint. However, a scenario may so arise that you need to show data to users at different permissions level, but the amount of data shown significantly varies with the permission. For example, for the settings API in our case. There are few informations like the app name, app tagline that we want to be available to users at all permission levels. However, informations such as aws secret key, or mailing secret keys or any other secret key, we want that to be available only to the admin and super admin. And the responses should be such that users at different permission level should feel that whatever information shown to them is complete and not missing. So, what we do is we create different schemas, in our case 2 different schemas. Depending on the permission of the user, we show them a particular schema. In our case, the two schemas are SettingSchemaAdmin and SettingSchemaNonAdmin. In SettingSchemaAdmin, we have all the attributes or fields that are present and is accessible to the Admin and Super Admin. In the SettingSchemaNonAdmin however, we have only those fields and attributes that we want to show to all non admin users. from flask_jwt import current_identity class SettingDetail(ResourceDetail): """ setting detail by id """ def before_get(self, args, kwargs): kwargs['id'] = 1 if current_identity.is_admin or current_identity.is_super_admin: self.schema = SettingSchemaAdmin else: self.schema = SettingSchemaNonAdmin   The above code helps us achieve this. If you have read previous blogs about the API server, you would already know that we are using JWT for authenticating our users. In this code, we are importing current_identity from flask_jwt. Current_identity, returns us an object of the User type which has properties such as is_admin, is_super_admin, etc. to help us identify the permission level of that user. Using this object, we check whether the user who is making the request via jwt authentication is an admin or super admin, or just a normal registered user. if current_identity.is_admin or current_identity.is_super_admin: self.schema = SettingSchemaAdmin else: self.schema = SettingSchemaNonAdmin   So, if the current user sending the request is an admin, then we set the schema for the Resource manager class of the flask-rest-jsonapi as SettingSchemaAdmin, which we have already declared before containing all the fields, else, we set it as SettingSchemaNonAdmin which has limited number of attributes.

Continue ReadingPermission Dependent Schema for Admin Settings in Open Event Server