diff --git a/app/extensions/api/namespace.py b/app/extensions/api/namespace.py index 8029397e..9fe176ab 100644 --- a/app/extensions/api/namespace.py +++ b/app/extensions/api/namespace.py @@ -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={ @@ -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` @@ -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( @@ -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"): """ diff --git a/flask_restplus_patched/__init__.py b/flask_restplus_patched/__init__.py index addc121b..9e2ece33 100644 --- a/flask_restplus_patched/__init__.py +++ b/flask_restplus_patched/__init__.py @@ -4,3 +4,4 @@ from .namespace import Namespace from .parameters import Parameters, PostFormParameters, PatchJSONParameters from .swagger import Swagger +from .resource import Resource diff --git a/flask_restplus_patched/namespace.py b/flask_restplus_patched/namespace.py index bd12c2f2..a38e7223 100644 --- a/flask_restplus_patched/namespace.py +++ b/flask_restplus_patched/namespace.py @@ -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 @@ -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 diff --git a/flask_restplus_patched/resource.py b/flask_restplus_patched/resource.py new file mode 100644 index 00000000..37c10dfd --- /dev/null +++ b/flask_restplus_patched/resource.py @@ -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)})