def wrapper(*args, **kwargs): headers = {'Content-Type': 'application/vnd.api+json'} try: return func(*args, **kwargs) except JsonApiException as e: return make_response(jsonify(jsonapi_errors([e.to_dict()])), e.status, headers) except Exception as e: if current_app.config['DEBUG'] is True or current_app.config.get( 'PROPAGATE_EXCEPTIONS') is True: raise if 'sentry' in current_app.extensions: current_app.extensions['sentry'].captureException() exc = JsonApiException(getattr( e, 'detail', current_app.config.get('GLOBAL_ERROR_MESSAGE') or str(e)), source=getattr(e, 'source', ''), title=getattr(e, 'title', None), status=getattr(e, 'status', None), code=getattr(e, 'code', None), id_=getattr(e, 'id', None), links=getattr(e, 'links', None), meta=getattr(e, 'meta', None)) return make_response(jsonify(jsonapi_errors([exc.to_dict()])), exc.status, headers)
def internal_server_error(error): if current_app.config['PROPOGATE_ERROR'] is True: exc = JsonApiException({'pointer': ''}, str(error)) else: exc = JsonApiException({'pointer': ''}, 'Unknown error') return make_response(json.dumps(jsonapi_errors([exc.to_dict()])), exc.status, {'Content-Type': 'application/vnd.api+json'})
def dispatch_request(self, *args, **kwargs): """Logic of how to handle a request""" method = getattr(self, request.method.lower(), None) if method is None and request.method == 'HEAD': method = getattr(self, 'get', None) assert method is not None, 'Unimplemented method {}'.format(request.method) headers = {'Content-Type': 'application/vnd.api+json'} try: response = method(*args, **kwargs) except JsonApiException as e: return make_response(jsonify(jsonapi_errors([e.to_dict()])), e.status, headers) except Exception as e: if current_app.config['DEBUG'] is True: raise e if 'sentry' in current_app.extensions: current_app.extensions['sentry'].captureException() exc = JsonApiException(getattr(e, 'detail', current_app.config.get('GLOBAL_ERROR_MESSAGE') or str(e)), source=getattr(e, 'source', ''), title=getattr(e, 'title', None), status=getattr(e, 'status', None), code=getattr(e, 'code', None), id_=getattr(e, 'id', None), links=getattr(e, 'links', None), meta=getattr(e, 'meta', None)) return make_response(jsonify(jsonapi_errors([exc.to_dict()])), exc.status, headers) if isinstance(response, Response): response.headers.add('Content-Type', 'application/vnd.api+json') return response if not isinstance(response, tuple): if isinstance(response, dict): response.update({'jsonapi': {'version': '1.0'}}) return make_response(jsonify(response), 200, headers) try: data, status_code, headers = response headers.update({'Content-Type': 'application/vnd.api+json'}) except ValueError: pass try: data, status_code = response except ValueError: pass if isinstance(data, dict): data.update({'jsonapi': {'version': '1.0'}}) return make_response(jsonify(data), status_code, headers)
def get_token_header(): auth = request.headers.get('authorization', None) if not auth: raise JsonApiException(title='authorization header missing', detail='authorization header is expected', status=401) parts = auth.split() if parts[0].lower() != 'bearer': raise JsonApiException( title='invalid header', detail='authorization header must start with Bearer', status=401) elif len(parts) == 1: raise JsonApiException(title='invalid header', detail='token not found', status=401) elif len(parts) > 2: raise JsonApiException( title='invalid header', detail='authorization header must be Bearer token', status=401) token = parts[1] return token
def delete_relationship(self, json_data, relationship_field, related_id_field, view_kwargs): """Delete a relationship :param dict json_data: the request params :param str relationship_field: the model attribute used for relationship :param str related_id_field: the identifier field of the related model :param dict view_kwargs: kwargs from the resource view """ self.before_delete_relationship(json_data, relationship_field, related_id_field, view_kwargs) obj = self.get_object(view_kwargs) if obj is None: url_field = getattr(self, 'url_field', 'id') filter_value = view_kwargs[url_field] raise ObjectNotFound('{}: {} not found'.format( self.model.__name__, filter_value), source={'parameter': url_field}) if not hasattr(obj, relationship_field): raise RelationNotFound("{} has no attribute {}".format( obj.__class__.__name__, relationship_field)) related_model = getattr(obj.__class__, relationship_field).property.mapper.class_ updated = False if isinstance(json_data['data'], list): obj_ids = { str(getattr(obj__, related_id_field)) for obj__ in getattr(obj, relationship_field) } for obj_ in json_data['data']: if obj_['id'] in obj_ids: getattr(obj, relationship_field).remove( self.get_related_object(related_model, related_id_field, obj_)) updated = True else: setattr(obj, relationship_field, None) updated = True try: self.session.commit() except JsonApiException as e: self.session.rollback() raise e except Exception as e: self.session.rollback() raise JsonApiException("Delete relationship error: " + str(e)) self.after_delete_relationship(obj, updated, json_data, relationship_field, related_id_field, view_kwargs) return obj, updated
def update_object(self, obj, data, view_kwargs): """Update an object through sqlalchemy :param DeclarativeMeta obj: an object from sqlalchemy :param dict data: the data validated by marshmallow :param dict view_kwargs: kwargs from the resource view :return boolean: True if object have changed else False """ if obj is None: url_field = getattr(self, 'url_field', 'id') filter_value = view_kwargs[url_field] raise ObjectNotFound('{}: {} not found'.format( self.model.__name__, filter_value), source={'parameter': url_field}) self.before_update_object(obj, data, view_kwargs) relationship_fields = get_relationships(self.resource.schema, model_field=True) for key, value in data.items(): if hasattr(obj, key) and key not in relationship_fields: setattr(obj, key, value) self.apply_relationships(data, obj) try: self.session.commit() except Exception as e: self.session.rollback() raise JsonApiException("Update object error: " + str(e), source={'pointer': '/data'}) self.after_update_object(obj, data, view_kwargs)
def before_get_object(self, view_kwargs): """Before retrieving requested details of a user, check all is OK""" # pylint: disable=no-self-use # Find the user and check access permission if we got here through: # - GET /api/v1/users/<id> if view_kwargs.get('id') is not None: if (g.current_user.is_administrator() or g.current_user.is_usermanager() or g.current_user.id == view_kwargs['id']): pass else: # Unauthorized raise JsonApiException(' ', title=HTTP_STATUS_CODES[403], status='403') # - GET /api/v1/categories/<category_id>/user if view_kwargs.get('category_id') is not None: user = find_user_by_category_id(view_kwargs['category_id']) view_kwargs['id'] = user.id # - GET /api/v1/items/<item_id>/user if view_kwargs.get('item_id') is not None: user = find_user_by_item_id(view_kwargs['item_id']) view_kwargs['id'] = user.id
def delete_object(self, obj, view_kwargs): """Delete an object through sqlalchemy :param DeclarativeMeta item: an item from sqlalchemy :param dict view_kwargs: kwargs from the resource view """ if obj is None: url_field = getattr(self, 'url_field', 'id') filter_value = view_kwargs[url_field] raise ObjectNotFound('{}: {} not found'.format( self.model.__name__, filter_value), source={'parameter': url_field}) self.before_delete_object(obj, view_kwargs) self.session.delete(obj) try: self.session.commit() except JsonApiException as e: self.session.rollback() raise e except Exception as e: self.session.rollback() raise JsonApiException("Delete object error: " + str(e)) self.after_delete_object(obj, view_kwargs)
def update_object(self, obj, data, **view_kwargs): """Update an object through sqlalchemy :param DeclarativeMeta obj: an object from sqlalchemy :param dict data: the data validated by marshmallow :param dict view_kwargs: kwargs from the resource view :return boolean: True if object have changed else False """ self.before_update_object(obj, data, **view_kwargs) update = False relationship_fields = get_relationships(self.resource.schema) for field in data: if hasattr(obj, field) and field not in relationship_fields: if getattr(obj, field) != data[field]: update = True setattr(obj, field, data[field]) update_relationship = self.apply_relationships(data, obj) if update_relationship is True: update = True try: self.session.commit() except Exception as e: self.session.rollback() raise JsonApiException({'pointer': '/data'}, "Update object error: " + str(e)) return update
def create_object(self, data, view_kwargs): """Create an object through sqlalchemy :param dict data: the data validated by marshmallow :param dict view_kwargs: kwargs from the resource view :return DeclarativeMeta: an object from sqlalchemy """ self.before_create_object(data, view_kwargs) relationship_fields = get_relationships(self.resource.schema, model_field=True) obj = self.model( **{ key: value for (key, value) in data.items() if key not in relationship_fields }) self.apply_relationships(data, obj) self.session.add(obj) try: self.session.commit() except Exception as e: self.session.rollback() raise JsonApiException("Object creation error: " + str(e), source={'pointer': '/data'}) self.after_create_object(obj, data, view_kwargs) return obj
def update_object(self, obj, data, view_kwargs): """Update an object through sqlalchemy :param DeclarativeMeta obj: an object from sqlalchemy :param dict data: the data validated by marshmallow :param dict view_kwargs: kwargs from the resource view :return boolean: True if object have changed else False """ self.before_update_object(obj, data, view_kwargs) relationship_fields = get_relationships(self.resource.schema) for key, value in data.items(): if hasattr(obj, key) and key not in relationship_fields: setattr(obj, key, value) self.apply_relationships(data, obj) try: self.session.commit() except Exception as e: self.session.rollback() raise JsonApiException({'pointer': '/data'}, "Update object error: " + str(e)) self.after_update_object(obj, data, view_kwargs)
def update_relationship(self, json_data, relationship_field, related_id_field, view_kwargs): """Update a relationship :param dict json_data: the request params :param str relationship_field: the model attribute used for relationship :param str related_id_field: the identifier field of the related model :param dict view_kwargs: kwargs from the resource view :return boolean: True if relationship have changed else False """ self.before_update_relationship(json_data, relationship_field, related_id_field, view_kwargs) obj = self.get_object(view_kwargs) if obj is None: url_field = getattr(self, 'url_field', 'id') filter_value = view_kwargs[url_field] raise ObjectNotFound('{}: {} not found'.format(self.model.__name__, filter_value), source={'parameter': url_field}) if not hasattr(obj, relationship_field): raise RelationNotFound("{} has no attribute {}".format(obj.__class__.__name__, relationship_field)) related_model = getattr(obj.__class__, relationship_field).property.mapper.class_ updated = False if isinstance(json_data['data'], list): related_objects = [] for obj_ in json_data['data']: related_objects.append(self.get_related_object(related_model, related_id_field, obj_)) obj_ids = {getattr(obj__, related_id_field) for obj__ in getattr(obj, relationship_field)} new_obj_ids = {getattr(related_object, related_id_field) for related_object in related_objects} if obj_ids != new_obj_ids: setattr(obj, relationship_field, related_objects) updated = True else: related_object = None if json_data['data'] is not None: related_object = self.get_related_object(related_model, related_id_field, json_data['data']) obj_id = getattr(getattr(obj, relationship_field), related_id_field, None) new_obj_id = getattr(related_object, related_id_field, None) if obj_id != new_obj_id: setattr(obj, relationship_field, related_object) updated = True try: self.session.commit() except Exception as e: self.session.rollback() raise JsonApiException("Update relationship error: " + str(e)) self.after_update_relationship(obj, updated, json_data, relationship_field, related_id_field, view_kwargs) return obj, updated
def handle_expired_signature(token, err): """Handles tokens with expired signatures.""" msg = 'Token provided by {} has expired'.format( jwt.get_unverified_claims(token).get('sub', 'sub not found')) current_app.logger.info(msg) raise JsonApiException(detail='{0}'.format(err), title='token expired', status=401)
def dispatch_request(self, *args, **kwargs): """Logic of how to handle a request""" method = getattr(self, request.method.lower(), None) if method is None and request.method == 'HEAD': method = getattr(self, 'get', None) assert method is not None, 'Unimplemented method {}'.format( request.method) headers = {'Content-Type': 'application/vnd.api+json'} try: response = method(*args, **kwargs) except JsonApiException as e: return make_response(jsonify(jsonapi_errors([e.to_dict()])), e.status, headers) except Exception as e: if current_app.config['DEBUG'] is True: raise e exc = JsonApiException('', str(e)) return make_response(jsonify(jsonapi_errors([exc.to_dict()])), exc.status, headers) if isinstance(response, Response): response.headers.add('Content-Type', 'application/vnd.api+json') return response if not isinstance(response, tuple): if isinstance(response, dict): response.update({'jsonapi': {'version': '1.0'}}) return make_response(jsonify(response), 200, headers) try: data, status_code, headers = response headers.update({'Content-Type': 'application/vnd.api+json'}) except ValueError: pass try: data, status_code = response except ValueError: pass if isinstance(data, dict): data.update({'jsonapi': {'version': '1.0'}}) return make_response(jsonify(data), status_code, headers)
def create_relationship(self, json_data, relationship_field, related_id_field, **view_kwargs): """Create a relationship :param dict json_data: the request params :param str relationship_field: the model attribut used for relationship :param str related_id_field: the identifier field of the related model :param dict view_kwargs: kwargs from the resource view :return boolean: True if relationship have changed else False """ obj = self.get_object(**view_kwargs) if not hasattr(obj, relationship_field): raise RelationNotFound( '', "{} has no attribut {}".format(obj.__class__.__name__, relationship_field)) related_model = getattr(obj.__class__, relationship_field).property.mapper.class_ updated = False if isinstance(json_data['data'], list): obj_ids = { str(getattr(obj__, related_id_field)) for obj__ in getattr(obj, relationship_field) } for obj_ in json_data['data']: if obj_['id'] not in obj_ids: getattr(obj, relationship_field).append( self.get_related_object(related_model, related_id_field, obj_)) updated = True else: related_object = None if json_data['data'] is not None: related_object = self.get_related_object( related_model, related_id_field, json_data['data']) obj_id = getattr(getattr(obj, relationship_field), related_id_field, None) new_obj_id = getattr(related_object, related_id_field, None) if obj_id != new_obj_id: setattr(obj, relationship_field, related_object) updated = True try: self.session.commit() except Exception as e: self.session.rollback() raise JsonApiException('', "Create relationship error: " + str(e)) return obj, updated
def delete_object(self, obj, **view_kwargs): """Delete an object through sqlalchemy :param DeclarativeMeta item: an item from sqlalchemy :param dict view_kwargs: kwargs from the resource view """ self.before_delete_object(obj, **view_kwargs) self.session.delete(obj) try: self.session.commit() except Exception as e: self.session.rollback() raise JsonApiException('', "Delete object error: " + str(e))
def get_jwks(): rv = cache.get('jwks') if rv is None: try: jwksurl = requests.get(current_app.config['JWKS_URL'], timeout=5) except requests.exceptions.Timeout: raise JsonApiException( title='JWKS Request Timed Out', detail= 'the authentication server is unavailable, or another issue has occured', status=500) rv = jwksurl.json() cache.set('jwks', rv, expire=48 * 3600) return rv
def before_create_object(self, data, unused_view_kwargs): """Prior to creating a new user, check all is OK""" # POST /users if ('email' not in data or 'password' not in data or 'first_name' not in data or 'last_name' not in data): raise BadRequest('Must include email, password, first_name and' 'last_name fields') user = self.session.query(User).filter_by(email=data['email']).first() if user: if user.blocked: raise JsonApiException(detail='Account has been blocked.' 'Contact the site administrator.', title='Permission denied', status='403') else: raise BadRequest('Email {} already registered'.format( data['email']))
def after_create_object(self, geokret, data, view_kwargs): # Create first move if requested if self.__create_first_move: # But only if user has home coordinates if geokret.owner.latitude and geokret.owner.longitude: move = Move( author=geokret.owner, geokret=geokret, type=MOVE_TYPE_DIPPED, moved_on_datetime=datetime.utcnow(), latitude=geokret.owner.latitude, longitude=geokret.owner.longitude, comment="Born here", ) geokret.last_move = move db.session.add(move) db.session.add(geokret) try: db.session.commit() except Exception as e: # pragma: no cover db.session.rollback() raise JsonApiException("Failed to create first move: " + str(e), source={'pointer': '/data'}) return geokret
def has_permission(resource_id, **kw): ''' Pre-processor for DELETE_RESOURCE Checks if: the resource being deleted is 'owned' by the requester the requester has admin privs/scope ''' token = get_token_header() unverified_claims = jwt.get_unverified_claims(token) token_scopes = unverified_claims['scope'].split() if 'delete:other-comments' in token_scopes: return # Check if owner of resource user_id = unverified_claims['sub'].split('|') user = models.User.query.filter_by(user_id=user_id).first() is_owner = models.Comment.query.get(resource_id).owner_is(user) if not is_owner: raise JsonApiException(title='invalid header', detail='not the owner of this resource', status=401)
def wrapped_f(*args, **kwargs): raise JsonApiException('', "Acces to this method have been disallowed", title="MethodNotAllowed", status=405)
def handle_claims(err): """Handles tokens with invalid claims.""" raise JsonApiException(detail='{0}'.format(err), title='invalid claim', status=401)
def handle_jwt(err): """Handles tokens with other jwt-related issues.""" raise JsonApiException(detail='{0}'.format(err), title='invalid signature', status=401)
def handle_non_jwt(): """Handles everything else.""" raise JsonApiException(title='invalid header', detail='unable to parse authentication token')