I have a Flask application using flask-restx and flask-login. I would like all routes by default to require login, and explicitly define public routes that require no authentication. I have started using decorators following the example given in this question:
Best way to make Flask-Login's login_required the default
It works for function endpoints, but not for restx resource endpoints.
I have tried adding the function both as a decorator, and using the method_decorators field. For example:
def public_route(decorated_function):
"""
This is a decorator to specify public endpoints in our flask routes
:param decorated_function:
:return:
"""
decorated_function.is_public = True
return decorated_function
class HelloWorld(ConfigurableResource):
method_decorators = {"get": [public_route]}
#public_route
#api.doc('Welcome message')
def get(self):
return {'hello': 'world'}
And this test passes:
def test_hello_world_is_public():
api = Namespace('health', description='Health related operations')
hello = HelloWorld(api, config=None, logger=None)
is_public_endpoint = getattr(hello.get, 'is_public', False)
assert is_public_endpoint
My challenge is I can't see how to access this attribute in my auth logic:
#app.before_request
def check_route_access():
"""
This function decides whethere access should be granted to an endpoint.
This function runs before all requests.
:return:
"""
is_public_endpoint = getattr(app.view_functions[request.endpoint], 'is_public', False)
if person_authorized_for_path(current_user, request.path, is_public_endpoint):
return
# Otherwise access not granted
return redirect(url_for("auth.index"))
This works for plain function endpoints, but not restx resources.
I understand that restx is wrapping my resource class in a function so that flask can do dispatch, but I can't figure out how to access the decorator from here. So I have some questions:
Is it possible to reach the decorator from the view_function?
Is it possible to know whether the endpoint is a restx resource or a plain rest function?
Is there a better way to do what I'm trying to achieve?
Based on this and this, method_decorators variable should be a list of functions, so you should use it like:
def _perform_auth(method):
is_public_endpoint = getattr(method, 'is_public', False)
# place the validation here
class Resource(flask_restx.Resource):
method_decorators = [_perform_auth]
Is it possible to reach the decorator from the view_function?
Well... it is possible, but I wouldn't recommend it. Here's an example
Is it possible to know whether the endpoint is a restx resource or a
plain rest function?
You probably can inspect the func and figure out if it's from restx, maybe looking at __qualname__, but then again, I`d wouldn't recommend it.
Is there a better way to do what I'm trying to achieve?
I would go one of these solutions:
Explicitly decorate view_funcs and resources that do need authentication, instead of the other way around
Create a blueprint for public endpoints, a blueprint for protected endpoints with the before_request decorator for authorization
Related
What is the proper way of testing throttling in DRF? I coulnd't find out any answer to this question on the net. I want to have separate tests for each endpoint since each one has custom requests limits (ScopedRateThrottle).
The important thing is that it can't affect other tests - they have to somehow run without throttling and limiting.
An easy solution is to patch the get_rate method of your throttle class. Thanks to tprestegard for this comment!
I have a custom class in my case:
from rest_framework.throttling import UserRateThrottle
class AuthRateThrottle(UserRateThrottle):
scope = 'auth'
In your tests:
from unittest.mock import patch
from django.core.cache import cache
from rest_framework import status
class Tests(SimpleTestCase):
def setUp(self):
cache.clear()
#patch('path.to.AuthRateThrottle.get_rate')
def test_throttling(self, mock):
mock.return_value = '1/day'
response = self.client.post(self.url, {})
self.assertEqual(
response.status_code,
status.HTTP_400_BAD_REQUEST, # some fields are required
)
response = self.client.post(self.url, {})
self.assertEqual(
response.status_code,
status.HTTP_429_TOO_MANY_REQUESTS,
)
It is also possible to patch the method in the DRF package to change the behavior of the standard throttle classes: #patch('rest_framework.throttling.SimpleRateThrottle.get_rate')
Like people already mentioned, this doesn't exactly fall within the scope of unit tests, but still, how about simply doing something like this:
from django.core.urlresolvers import reverse
from django.test import override_settings
from rest_framework.test import APITestCase, APIClient
class ThrottleApiTests(APITestCase):
# make sure to override your settings for testing
TESTING_THRESHOLD = '5/min'
# THROTTLE_THRESHOLD is the variable that you set for DRF DEFAULT_THROTTLE_RATES
#override_settings(THROTTLE_THRESHOLD=TESTING_THRESHOLD)
def test_check_health(self):
client = APIClient()
# some end point you want to test (in this case it's a public enpoint that doesn't require authentication
_url = reverse('check-health')
# this is probably set in settings in you case
for i in range(0, self.TESTING_THRESHOLD):
client.get(_url)
# this call should err
response = client.get(_url)
# 429 - too many requests
self.assertEqual(response.status_code, 429)
Also, regarding your concerns of side-effects, as long as you do user creation in setUp or setUpTestData, tests will be isolated (as they should), so no need to worry about 'dirty' data or scope in that sense.
Regarding cache clearing between tests, I would just add cache.clear() in tearDown or try and clear the specific key defined for throttling.
I implemented my own caching mechanism for throttling based on the user and the parameters with which a request is called. You can override SimpleRateThrottle.get_cache_key to get this behavior.
Take this throttle class for example:
class YourCustomThrottleClass(SimpleRateThrottle):
rate = "1/d"
scope = "your-custom-throttle"
def get_cache_key(self, request: Request, view: viewsets.ModelViewSet):
# we want to throttle the based on the request user as well as the parameter
# `foo` (i.e. the user can make a request with a different `foo` as many times
# as they want in a day, but only once a day for a given `foo`).
foo_request_param = view.kwargs["foo"]
ident = f"{request.user.pk}_{foo_request_param}"
# below format is copied from `UserRateThrottle.get_cache_key`.
return self.cache_format % {"scope": self.scope, "ident": ident}
In order to clear this in a TestCase I call the following method in each test method as required:
def _clear_throttle_cache(self, request_user, foo_param):
# we need to clear the cache of the throttle limits already stored there.
throttle = YourCustomThrottleClass()
# in the below two lines mock whatever attributes on the request and
# view instances are used to calculate the cache key in `.get_cache_key`
# which you overrode. Here we use `request.user` and `view.kwargs["foo"]`
# to calculate the throttle key, so we mock those.
pretend_view = MagicMock(kwargs={foo: foo_param})
pretend_request = MagicMock(user=request_user)
# this is the method you overrode in `YourCustomThrottleClass`.
throttle_key = throttle.get_cache_key(pretend_request, pretend_view)
throttle.cache.delete(user_key)
This is an amendment on yofee's post which got me 90% there. When using a throttle, custom or otherwise, with a set rate, get_rate is never called. As shown below from the source.
def __init__(self):
if not getattr(self, 'rate', None):
self.rate = self.get_rate()
Hence when one is mocking a throttle with a set rate that is not None, I would recommend patching the rate attribute directly.
...
with mock.patch.object(AuthRateThrottle, 'rate', '1/day'):
...
flask-sentinel creates a route to a management UI using app.add_url_rule. Wanting to apply some access control rules I wrapped it like this:
from flask.ext import sentinel
def requires_auth(fn):
#wraps(fn)
def decorated(*args, **kwargs):
roles = []
if 'user' in session and 'roles' in session['user']:
roles = session['user']['roles']
if 'admin' not in roles:
flash("Not enough power")
return redirect('/')
return fn(*args, **kwargs)
return decorated
sentinel.views.management = requires_auth(sentinel.views.management)
sentinel.ResourceOwnerPasswordCredentials(app)
The question is, is this really the way or is there some more appropriate way?
EDIT:
Realized that my question was rather abstract and really just about flask instead of sentinel. Guess what I meant to ask was: "is there any more declarative way to apply security restrictions to paths in Flask, instead of wrapping each registered route one by one?"
After a bit of study, this seems to provide the flexible security control I was looking for.
from flask import Flask, request
app = Flask('bla')
PATH_ROLES = {
'/admin/.*': ['admin']
}
#app.before_request
def before_request():
try:
rule = next(x for x in PATH_ROLES if re.match(x, request.path))
print "path requires: ", PATH_ROLES[rule]
except StopIteration: pass
At quick glance I'd say that your approach is sound. You are essentially applying the common Flask pattern of wrapping a view with a decorator.
I have simple Resource class which defines some API methods:
class RoomAPI(Resource):
def get(self):
# some code
def post(self):
# some code
def put(self):
# some code
Then I define my routes like this:
api.add_resource(RoomAPI,'/api/rooms/')
So, my question is: how can I make different routes for each HTTP methos using only one Resource class?
I want to get such API:
GET /api/rooms/get/
POST /api/rooms/create/
PUT /api/rooms/update/
The short answer is, you shouldn't. That's not RESTful at all.
However, if you really want to, I think you could do it like so:
api.add_resource(RoomAPI,'/api/rooms/get', methods=['GET'])
api.add_resource(RoomAPI,'/api/rooms/create', methods=['PUT'])
api.add_resource(RoomAPI,'/api/rooms/update', methods=['POST'])
Since the unused **kwargs from add_resource get passed to add_url_rule().
I want to log (send metrics to statsD ) every GET/POST request in GAE.
So thats my decorator :
def log_request():
def actual_decorator(method):
def wrapper(self, *args, **kwargs):
// send metrics that have the request name as the name space
return method(self, *args, **kwargs)
return wrapper
return actual_decorator
Of course i don't want to go over all my GET requests and put a #log_request() out there. Is there a way to catch them all on a global level ?
(not using Django)
I do not think it's possible (in Python) to inherit a decorated method automatically to all subclasses. You must explicitly declare decorators for each method.
If all you are looking for is analytics, you can use the logging service and a ScheduledTask to accomplish the same.
I have implemented similar thing recently, you can wrap the WSGIApplication in a middleware that log the request. I do not know about statsD, in my case I log the request to datastore.
I would like to store some information at the "request scope" when using google app engine (python). What I mean by this is that I would like to initialize some information when a request is first received, and then be able to access it from anywhere for the life of the request (and only from that request).
An example of this would be if I saved the current user's name at request scope after they were authenticated.
How would I go about doing this sort of thing?
Thanks!
A pattern used in app engine itself seems to be threading.local which you can grep for in the SDK code. Making os.environ request local is done like that in runtime/request_environment.py for example.
A rough example:
import threading
class _State(threading.local):
"""State keeps track of request info"""
user = None
_state = _State()
From elsewhere you could authenticate early on in handler code.
from state import _state
if authentication_passed:
_state.user = user
and provide convenience that can be used in other parts of your code
from state import _state
def get_authenticated_user():
user = _state.user
if not user:
raise AuthenticationError()
return user
You need something like this:-
class BaseHandler(webapp2.RequestHandler):
#A function which is useful in order to determine whether user is logged in
def initialize(self, *a, **kw):
#Do the authentication
self.username = username
class MainHandler(BaseHandler):
def get(self):
print self.username
Now if you inherit BaseHandler class all the request will first go through the initialize method of BaseHandler class and since in the BaseHandler class you are setting the username
and MainHandler inherits form BaseHandler you will have the self.username defined and all the request wil go through initialize method.