Skip to content

Instantly share code, notes, and snippets.

@theholy7
Created December 23, 2019 19:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save theholy7/08f92a4902936f0efc41dcd4b01337a7 to your computer and use it in GitHub Desktop.
Save theholy7/08f92a4902936f0efc41dcd4b01337a7 to your computer and use it in GitHub Desktop.
class AuthorizedResource(Resource):
"""
We created this AuthorizedResource from Resource
because the user authorization was being made after payload
validation. This way a non-auth'ed user was able to sniff the
payload that was required by the endpoint.
By wrapping dispatch_request with the requires_auth function,
we check for user authentication first.
"""
@requires_auth
def dispatch_request(self, *args, **kwargs):
return super().dispatch_request(*args, **kwargs)
def dispatch_request(self, *args, **kwargs):
# Taken from flask
meth = getattr(self, request.method.lower(), None)
if meth is None and request.method == 'HEAD':
meth = getattr(self, 'get', None)
assert meth is not None, 'Unimplemented method %r' % request.method
for decorator in self.method_decorators:
meth = decorator(meth)
self.validate_payload(meth)
resp = meth(*args, **kwargs)
if isinstance(resp, BaseResponse):
return resp
representations = self.representations or {}
mediatype = request.accept_mimetypes.best_match(representations, default=None)
if mediatype in representations:
data, code, headers = unpack(resp)
resp = representations[mediatype](data, code, headers)
resp.headers['Content-Type'] = mediatype
return resp
return resp
def validate(self, data, resolver=None, format_checker=None):
"""
Override the parent class validate method to raise custom errors
in our API when payloads don't follow the schema rules.
This collects a list of errors and delivers it to the user so one
request is enough to get all the errors.
"""
validator = Draft4Validator(self.__schema__, resolver=resolver, format_checker=format_checker)
type_errors = [err for err in validator.iter_errors(data)]
if len(type_errors) >= 1:
# Raise for multiple errors
exceptions = [self._convert_type_error_to_custom(err) for err in type_errors]
raise MultipleErrorException(exceptions)
from flask_restplus import Model
from jsonschema import Draft4Validator
class ValidationModel(Model):
"""
Extend the Model class provided by flask-restplus so that we can add the
additionalProperties key to the schema, thus preventing additional properties to
be sent in the payload.
"""
def __init__(self, *args, **kwargs):
# pop strict, otherwise it will end up in __apidoc__ and things crash
self.strict = kwargs.pop('strict', False)
super().__init__(*args, **kwargs)
@property
def _schema(self):
"""
Get the base schema from the parent class which handles all the logic
to convert the fields into json schemas, and add `additionalProperties`
to the schema of the current object, to prevent new properties
from being passed in the payload.
:returns dict: dictionary containing schema
"""
base_schema = super()._schema
if self.strict:
base_schema['additionalProperties'] = False
return base_schema
def __deepcopy__(self, memo):
"""
If you create a new class you NEED
to overwrite this method to take new parameters you
need in it.
Otherwise things will break, not exactly sure why.
"""
obj = self.__class__(self.name,
[(key, copy.deepcopy(value, memo)) for key, value in self.items()],
mask=self.__mask__, strict=self.strict)
obj.__parents__ = self.__parents__
return obj
class ValidationNamespace(Namespace):
"""
Validation Namespace extends the normal Namespace class to
overwrite the model function, so that we can register a ValidationModel
by adding directly to the namespace with `ns.model(...)`.
"""
def model(self, name=None, model=None, mask=None, **kwargs):
"""
Register a validation model so that classes created with the model method
can receive the `strict` kwarg.
"""
model = ValidationModel(name, model, mask=mask, **kwargs)
model.__apidoc__.update(kwargs)
return self.add_model(name, model)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment