Skip to content

Commit

Permalink
Closes #7: Implement OPTIONS method (#29)
Browse files Browse the repository at this point in the history
Create Resource patched in flask_restplus_patched that inherits restplus Resource and define the `options` method there.
Patch some of decorators to be possible to be impemented with classes. Patch some decorators to get access to permission decorators to perform permission checks in option.
  • Loading branch information
khorolets authored and frol committed Sep 26, 2016
1 parent 7dc29bc commit e17bde5
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 5 deletions.
16 changes: 14 additions & 2 deletions app/extensions/api/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,9 @@ def decorator(func):
permissions.ActivatedUserRolePermission()
)(func)

oauth_protected_func = oauth2.require_oauth(*oauth_scopes)(protected_func)
oauth_protection_decorator = oauth2.require_oauth(*oauth_scopes)
self._register_access_restriction_decorator(func, oauth_protection_decorator)
oauth_protected_func = oauth_protection_decorator(protected_func)

return self.doc(
security={
Expand Down Expand Up @@ -169,6 +171,7 @@ def wrapper(*args, **kwargs):
return wrapper

protected_func = _permission_decorator(func)
self._register_access_restriction_decorator(protected_func, _permission_decorator)

# Apply `_role_permission_applied` marker for Role Permissions,
# so don't apply unnecessary permissions in `login_required`
Expand All @@ -185,7 +188,7 @@ def wrapper(*args, **kwargs):
issubclass(permission, permissions.RolePermission)
)
):
protected_func._role_permission_applied = True # pylint: disable=protected-access
protected_func._role_permission_applied = True # pylint: disable=protected-access

permission_description = permission.__doc__.strip()
return self.doc(
Expand All @@ -199,6 +202,15 @@ def wrapper(*args, **kwargs):

return decorator

def _register_access_restriction_decorator(self, func, decorator_to_register):
"""
Helper function to register decorator to function to perform checks
in options method
"""
if not hasattr(func, '_access_restriction_decorators'):
func._access_restriction_decorators = [] # pylint: disable=protected-access
func._access_restriction_decorators.append(decorator_to_register) # pylint: disable=protected-access

@contextmanager
def commit_or_abort(self, session, default_error_message="The operation failed to complete"):
"""
Expand Down
1 change: 1 addition & 0 deletions flask_restplus_patched/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
from .namespace import Namespace
from .parameters import Parameters, PostFormParameters, PatchJSONParameters
from .swagger import Swagger
from .resource import Resource
20 changes: 17 additions & 3 deletions flask_restplus_patched/namespace.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,17 @@ def resolve_object(self, object_arg_name, resolver):
... def get(self, user):
... # user is a User instance here
"""
def decorator(func):
@wraps(func)
def decorator(func_or_class):
if isinstance(func_or_class, type):
func_or_class.method_decorators = (
[decorator] + func_or_class.method_decorators
)
return func_or_class

@wraps(func_or_class)
def wrapper(*args, **kwargs):
kwargs[object_arg_name] = resolver(kwargs)
return func(*args, **kwargs)
return func_or_class(*args, **kwargs)
return wrapper
return decorator

Expand Down Expand Up @@ -167,3 +173,11 @@ def decorator(func_or_class):
return self.doc(responses={code: (description, api_model)})(decorated_func_or_class)

return decorator

def route(self, *args, **kwargs):
base_wrapper = super(Namespace, self).route(*args, **kwargs)
def wrapper(cls):
if 'OPTIONS' in cls.methods:
cls.options = self.response(code=204)(cls.options)
return base_wrapper(cls)
return wrapper
41 changes: 41 additions & 0 deletions flask_restplus_patched/resource.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
# pylint: disable=protected-access
import flask
from flask_restplus import Resource as OriginalResource
from werkzeug.exceptions import HTTPException


class Resource(OriginalResource):
"""
Extended Flast-RESTPlus Resource to add options method
"""

def __init__(self, *args, **kwargs):
super(Resource, self).__init__(*args, **kwargs)

def options(self, *args, **kwargs):
"""
Implementation of universal OPTIONS method for resources
This method checks every permissions provided as decorators
for other methods to provide information about what methods
current_user can use
"""
funcs = [getattr(self, m.lower()) for m in self.methods]
methods = self.methods[:]
for method in funcs:
if hasattr(method, '_access_restriction_decorators'):
if not hasattr(method, '_cached_ok_func'):
ok_func = lambda *args, **kwargs: True
for decorator in method._access_restriction_decorators:
ok_func = decorator(ok_func)
method.__dict__['_cached_ok_func'] = ok_func
else:
ok_func = method._cached_ok_func
try:
ok_func(*args, **kwargs)
except HTTPException:
del methods[methods.index(method.__name__.upper())]
else:
pass # !!! all checks are passed, so we should be fine here!
return flask.Response(status=204, headers={'Allow': ", ".join(methods)})

0 comments on commit e17bde5

Please sign in to comment.