Skip to content

Commit

Permalink
Fixed default OPTIONS method implementation to comply with `login_req…
Browse files Browse the repository at this point in the history
…uired` decorator
  • Loading branch information
frol committed Sep 29, 2016
1 parent d8f3525 commit 0d5facc
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 32 deletions.
21 changes: 13 additions & 8 deletions app/extensions/api/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,18 @@ def add_namespace(self, ns):
for method in resource.methods:
method_func = getattr(resource, method.lower())

if 'security' in method_func.__apidoc__:
if '__oauth__' in method_func.__apidoc__['security']:
oauth_scopes = method_func.__apidoc__['security']['__oauth__']['scopes']
method_func.__apidoc__['security'] = {
auth_name: oauth_scopes
for auth_name, auth_settings in iteritems(self.authorizations)
if auth_settings['type'].startswith('oauth')
}
if (
hasattr(method_func, '__apidoc__')
and
'security' in method_func.__apidoc__
and
'__oauth__' in method_func.__apidoc__['security']
):
oauth_scopes = method_func.__apidoc__['security']['__oauth__']['scopes']
method_func.__apidoc__['security'] = {
auth_name: oauth_scopes
for auth_name, auth_settings in iteritems(self.authorizations)
if auth_settings['type'].startswith('oauth')
}

super(Api, self).add_namespace(ns)
9 changes: 3 additions & 6 deletions app/modules/teams/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,12 @@ def post(self, args):
code=http_exceptions.NotFound.code,
description="Team not found.",
)
@api.resolve_object_by_model(Team, 'team')
class TeamByID(Resource):
"""
Manipulations with a specific team.
"""

@api.resolve_object_by_model(Team, 'team')
@api.permission_required(
permissions.OwnerRolePermission,
kwargs_on_request=lambda kwargs: {'obj': kwargs['team']}
Expand All @@ -87,7 +87,6 @@ def get(self, team):
return team

@api.login_required(oauth_scopes=['teams:write'])
@api.resolve_object_by_model(Team, 'team')
@api.permission_required(
permissions.OwnerRolePermission,
kwargs_on_request=lambda kwargs: {'obj': kwargs['team']}
Expand All @@ -109,7 +108,6 @@ def patch(self, args, team):
return team

@api.login_required(oauth_scopes=['teams:write'])
@api.resolve_object_by_model(Team, 'team')
@api.permission_required(
permissions.OwnerRolePermission,
kwargs_on_request=lambda kwargs: {'obj': kwargs['team']}
Expand All @@ -135,12 +133,12 @@ def delete(self, team):
code=http_exceptions.NotFound.code,
description="Team not found.",
)
@api.resolve_object_by_model(Team, 'team')
class TeamMembers(Resource):
"""
Manipulations with members of a specific team.
"""

@api.resolve_object_by_model(Team, 'team')
@api.permission_required(
permissions.OwnerRolePermission,
kwargs_on_request=lambda kwargs: {'obj': kwargs['team']}
Expand All @@ -155,7 +153,6 @@ def get(self, args, team):
return team.members[args['offset']: args['offset'] + args['limit']]

@api.login_required(oauth_scopes=['teams:write'])
@api.resolve_object_by_model(Team, 'team')
@api.permission_required(
permissions.OwnerRolePermission,
kwargs_on_request=lambda kwargs: {'obj': kwargs['team']}
Expand Down Expand Up @@ -192,13 +189,13 @@ def post(self, args, team):
code=http_exceptions.NotFound.code,
description="Team or member not found.",
)
@api.resolve_object_by_model(Team, 'team')
class TeamMemberByID(Resource):
"""
Manipulations with a specific team member.
"""

@api.login_required(oauth_scopes=['teams:write'])
@api.resolve_object_by_model(Team, 'team')
@api.permission_required(
permissions.OwnerRolePermission,
kwargs_on_request=lambda kwargs: {'obj': kwargs['team']}
Expand Down
5 changes: 2 additions & 3 deletions app/modules/users/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,12 @@ def get(self):
code=http_exceptions.NotFound.code,
description="User not found.",
)
@api.resolve_object_by_model(User, 'user')
class UserByID(Resource):
"""
Manipulations with a specific user.
"""

@api.resolve_object_by_model(User, 'user')
@api.permission_required(
permissions.OwnerRolePermission,
kwargs_on_request=lambda kwargs: {'obj': kwargs['user']}
Expand All @@ -96,7 +96,6 @@ def get(self, user):
return user

@api.login_required(oauth_scopes=['users:write'])
@api.resolve_object_by_model(User, 'user')
@api.permission_required(
permissions.OwnerRolePermission,
kwargs_on_request=lambda kwargs: {'obj': kwargs['user']}
Expand All @@ -121,12 +120,12 @@ def patch(self, args, user):


@api.route('/me')
@api.login_required(oauth_scopes=['users:read'])
class UserMe(Resource):
"""
Useful reference to the authenticated user itself.
"""

@api.login_required(oauth_scopes=['users:read'])
@api.response(schemas.DetailedUserSchema())
def get(self):
"""
Expand Down
47 changes: 32 additions & 15 deletions flask_restplus_patched/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from flask_restplus import Resource as OriginalResource
from werkzeug.exceptions import HTTPException

from ._http import HTTPStatus


class Resource(OriginalResource):
"""
Expand Down Expand Up @@ -38,21 +40,36 @@ def options(self, *args, **kwargs):
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
method_funcs = [getattr(self, m.lower()) for m in self.methods]
allowed_methods = []
for method_func in method_funcs:
if getattr(method_func, '_access_restriction_decorators', None):
if not hasattr(method_func, '_cached_ok_func'):

This comment has been minimized.

Copy link
@khorolets

khorolets Sep 29, 2016

Collaborator

I think you need to change '_chached_ok_func' to '_cached_fake_method_func' here

This comment has been minimized.

Copy link
@frol

frol Sep 29, 2016

Author Owner

Indeed! Good catch!

This comment has been minimized.

Copy link
@frol

frol Sep 29, 2016

Author Owner

Fixed.

fake_method_func = lambda *args, **kwargs: True
# `__name__` is used in `login_required` decorator, so it
# is required to fake this also
fake_method_func.__name__ = 'options'

# Decorate the fake method with the registered access
# restriction decorators
for decorator in method_func._access_restriction_decorators:
fake_method_func = decorator(fake_method_func)

# Cache the `fake_method_func` to avoid redoing this over
# and over again
method_func.__dict__['_cached_fake_method_func'] = fake_method_func
else:
ok_func = method._cached_ok_func
fake_method_func = method._cached_fake_method_func

try:
ok_func(*args, **kwargs)
fake_method_func(self, *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)})
# This method is not allowed, so skip it
continue

allowed_methods.append(method_func.__name__.upper())

return flask.Response(
status=HTTPStatus.NO_CONTENT,
headers={'Allow': ", ".join(allowed_methods)}
)

0 comments on commit 0d5facc

Please sign in to comment.