Django Request Object in middleware? - python

I'm constructing a custom Prometheus middleware class to do some monitoring and I'd like to either construct or retrieve the Django Rest Framework Request object for a given request in middleware so that I can capture request.version in my metrics.
However, the request parameter in my code below appears to be a WSGIRequest object. Is it possible to discern the DRF HttpRequest object within middleware? Code Below...
if django.VERSION >= (1, 10, 0):
from django.utils.deprecation import MiddlewareMixin
else:
MiddlewareMixin = object
class CustomPrometheusAfterMiddleware(MiddlewareMixin):
def process_response(self, request, response):
# Do some cool things
return response

If you check the source code, the Django's HttpRequest object is wrapped in DRF's Request object in views.APIView.dispatch so I think you will have to go thru quite a few hoops to get it. Your best bet might be to patch APIView to store the infos you're after on the real request (Django's HttpRequest or subclass of) - or on the response FWIW - so you can get them back in the middleware.

Related

Calling View in API Request Factory when testing in Django Rest Framework

Whenever we test an API in Django Rest Framework using API Request Factory why do we call the view when we are already passing the url .
Look at the following code for better understanding .
request = self.factory.post("/api/v1/menu/", data)
views = MenuResource.as_view({"post": "post"})
response = views(request)
response.render()
self.assertEqual(201, response.status_code)
In the above code we are calling the url as well as calling the view . View is being called for rendering the view on the url but that is not what my use case is . I just want to test the response code . Is there a way of getting the response code without rendering the view as that is an over kill for my use case .
I have looked for other methods except for using API Request Factory but i just wanted to know why does API Request Factory need to call the view . Is there any advantage in comparison to other API Testing modules present in Django Rest Framework.
That is because you are not making a request to the URL.
request = self.factory.post("/api/v1/menu/", data) simply creates a request object for you to use it does not actually make the request.
So testing the view in your case consists of creating a request object and passing it to the view which mimics the way an actual request would be passed and processed by that view.
If you want to send actual request you need to mock a test server which will handle the actual requests.

Django REST: OPTIONS request is registered as PUT

Whenever the browser sends an OPTIONS request, Django REST registers that as a PUT request.
I was writing my permission code when I noticed it. I use the default request parameter that is passed into the def has_object_permission( self, request, view, obj ):.
Using request.method will return the correct request method for every request except for OPTIONS.
However, when I use request.method in a my get_permission( self, request, view ): function in a different part of the project, correct response is returned. Could there be something wrong with the has_object_permission() function?
Currently I just check if the request is PUT, as I believe PUT requests are not used by Django anyway. But it would be nice if I could use the actual name instead.
My Django REST version is 3.9.0
OPTIONS requests are often used for what are called "pre-flight" requests in Cross-origin resource sharing (CORS). This is not anything wrong with has_object_permission(), it's just that these pre-flight requests are probably not intended to handled by your view. There is a more detailed answer in this SO post: https://stackoverflow.com/a/29954326/784648

Setting cookies within Django Middleware

I would like to use Custom Django Middleware (Django 1.9) to check whether or not an anonymous user has accepted a site's terms & conditions - I'll show the user a dialog if they've not yet clicked 'Agree'.
I have no need to store this in the database and would prefer to simply use a Cookie. I have the following in settings.py:
MIDDLEWARE_CLASSES = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
...
'myapp.middleware.app_custom_middleware.TermsMiddleware',]
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
I'm then trying something very rudimentary in TermsMiddleware - here I'm deliberately trying to modify the request and the response just to try to get it to work:
class TermsMiddleware(object):
def process_request(self, request):
request.session['myCookieKey'] = 'myCookieValue'
request.session.save()
return
def process_response(self, request, response):
request.session['myCookieKey'] = 'myCookieValue'
request.session.save()
return response
If I inspect the response in the browser, I see that myCookieKey isn't being set, so I think I've misunderstood how this should be written.
Can I manipulate the session within middleware like this, having it take effect in the cookie sent in the response Django was going to send anyway? The documentation makes it sound like I should be able to:
It should return either None or an HttpResponse object. If it returns
None, Django will continue processing this request, executing any
other process_request() middleware, then, process_view() middleware,
and finally, the appropriate view. If it returns an HttpResponse
object, Django won’t bother calling any other request, view or
exception middleware, or the appropriate view; it’ll apply response
middleware to that HttpResponse, and return the result.
I've also seen the note in the documentation to use SessionStore() when outside of a view function. In theory I don't think I should need to do that here because I've got access to request (and response). I tried it anyway, and I still couldn't get it to work.
Help much appreciated!
The thing you've misunderstood is the relationship between the session and the cookies.
You can't set arbitrary cookies by modifying the session. The session is a collection of data, usually stored in the db or a file, and the only cookie is the one that contains the session key for the current user.
You certainly can modify the session in middleware, but wherever you do so, you shouldn't expect to see a cookie being set.
(Separately, you should never call double underscore methods like __setitem__ directly. The session is accessed via a dict-like interface: request.session['foo'] = 'bar'.)

Django: filtering expected content type?

Django offers a way to restrict the accepted methods using the #request_http_method decorator, so if a particular view can only respond to a GET request we can do:
#require_http_methods(['GET'])
def only_get(request):
pass
Otherwise we get a 403 (FORBIDDEN) response.
However, I would also like to accept a Content-Type of json. If it's not json it should reject the request as well (I am guessing a 403 response would also be the appropriate one).
Does Django have anything similar to the require_http_methods decorator, but for content types? If not, how else could I tackle this scenario?
I do not think that Django has something similar for Content-Type, but you can easily write your Middleware, that would drop requests with wrong Content-Type and then use decorator_from_middleware option.
If you use Django 1.10:
class AllowedContentTypes(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request, *args, **kwargs):
types = kwargs.pop('types') or ['application/json']
if request.content_type in types:
response = self.get_response(request)
else:
response = HttpResponse() # your response for wrong content type
return response
and apply it to your view like:
#decorator_from_middleware_with_args(AllowedContentTypes)(types=['application/json'])
def your_view(request):
...
Also, you can use Django REST Framework, there you are able to filter Content-Type using Parsers, JSONParser would only allow application/json content-type for requests. It is also would be more useful to implement REST API for you app.

Django and Middleware which uses request.user is always Anonymous

I'm trying to make middleware which alters some fields for the user based on subdomain, etc...
The only problem is the request.user always comes in as AnonymousUser within the middleware, but is then the correct user within the views. I've left the default authentication and session middleware django uses within the settings.
There is a similar question here: Django, request.user is always Anonymous User
But doesn't overly answer the total question because I'm not using different authentication methods, and djangos authentication is running before I invoke my own middleware.
Is there a way, while using DRF, to get the request.user within the middleware? I'll show some sample code here:
class SampleMiddleware(object):
def process_view(self, request, view_func, view_args, view_kwargs):
#This will be AnonymousUser. I need it to be the actual user making the request.
print (request.user)
def process_response(self, request, response):
return response
with process_request:
class SampleMiddleware(object):
def process_request(self, request):
#This will be AnonymousUser. I need it to be the actual user making the request.
print (request.user)
def process_response(self, request, response):
return response
I've solved this problem by getting DRF token from the requests and loading request.user to the user associated to that model.
I had the default django authentication and session middleware, but it seems DRF was using it's token auth after middleware to resolve the user (All requests were CORS requests, this might have been why). Here's my updated middleware class:
from re import sub
from rest_framework.authtoken.models import Token
from core.models import OrganizationRole, Organization, User
class OrganizationMiddleware(object):
def process_view(self, request, view_func, view_args, view_kwargs):
header_token = request.META.get('HTTP_AUTHORIZATION', None)
if header_token is not None:
try:
token = sub('Token ', '', header_token)
token_obj = Token.objects.get(key = token)
request.user = token_obj.user
except Token.DoesNotExist:
pass
#This is now the correct user
print (request.user)
This can be used on process_view or process_request as well.
Hopefully this can help someone out in the future.
Came across this today while having the same problem.
TL;DR;
Skip below for code example
Explanation
Thing is DRF have their own flow of things, right in the middle of the django request life-cycle.
So if the normal middleware flow is :
request_middleware (before starting to work on the request)
view_middleware (before calling the view)
template_middleware (before render)
response_middleware (before final response)
DRF code, overrides the default django view code, and executes their own code.
In the above link, you can see that they wrap the original request with their own methods, where one of those methods is DRF authentication.
So back to your question, this is the reason using request.user in a middleware is premature, as it only gets it's value after view_middleware** executes.
The solution I went with, is having my middleware set a LazyObject.
This helps, because my code (the actual DRF ApiVIew) executes when the actual user is already set by DRF's authentication.
This solution was proposed here together with a discussion.
Might have been better if DRF had a better way to extend their functionality, but as things are, this seems better than the provided solution (both performance and readability wise).
Code Example
from django.utils.functional import SimpleLazyObject
def get_actual_value(request):
if request.user is None:
return None
return request.user #here should have value, so any code using request.user will work
class MyCustomMiddleware(object):
def process_request(self, request):
request.custom_prop = SimpleLazyObject(lambda: get_actual_value(request))
The accepted answer only takes TokenAuthentication in consideration - in my case, there are more authentication methods configured. I thus went with initializing the DRF's Request directly which invokes DRF's authentication machinery and loops through all configured authentication methods.
Unfortunately, it still introduces an additional load on the database since the Token object must be queried (the accepted answer has this problem as well). The trick with SimpleLazyObject in this answer is a much better solution, but it didn't work for my use case because I need the user info in the middleware directly - I'm extending the metrics in django_prometheus and it processes the request before get_response is called.
from rest_framework.request import Request as RestFrameworkRequest
from rest_framework.views import APIView
class MyMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
drf_request: RestFrameworkRequest = APIView().initialize_request(request)
user = drf_request.user
...
return self.get_response(request)
Based on Daniel Dubovski's very elegant solution above, here's an example of middleware for Django 1.11:
from django.utils.functional import SimpleLazyObject
from organization.models import OrganizationMember
from django.core.exceptions import ObjectDoesNotExist
def get_active_member(request):
try:
active_member = OrganizationMember.objects.get(user=request.user)
except (ObjectDoesNotExist, TypeError):
active_member = None
return active_member
class OrganizationMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
request.active_member = SimpleLazyObject(lambda: get_active_member(request))
response = self.get_response(request)
# Code to be executed for each request/response after
# the view is called.
return response
Daniel Dubovski's solution is probably the best in most cases.
The problem with the lazy object approach is if you need to rely on the side effects. In my case, I need something to happen for each request, no matter what.
If I'd use a special value like request.custom_prop, it has to be evaluated for each request for the side effects to happen. I noticed that other people are setting request.user, but it doesn't work for me since some middleware or authentication class overwrites this property.
What if DRF supported its own middleware? Where could I plug it in? The easiest way in my case (I don't need to access the request object, only the authenticated user) seems to be to hook into the authentication class itself:
from rest_framework.authentication import TokenAuthentication
class TokenAuthenticationWithSideffects(TokenAuthentication):
def authenticate(self, request):
user_auth_tuple = super().authenticate(request)
if user_auth_tuple is None:
return
(user, token) = user_auth_tuple
# Do stuff with the user here!
return (user, token)
Then I could just replace this line in my settings:
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
#"rest_framework.authentication.TokenAuthentication",
"my_project.authentication.TokenAuthenticationWithSideffects",
),
# ...
}
I'm not promoting this solution, but maybe it will help someone else.
Pros:
It to solves this specific problem
There's no double authentication
Easy to maintain
Cons:
Not tested in production
Things happen in an unexpected place
Side effects...
I know it's not exactly answering the 'can we access that from the middleware' question, but I think it's a more elegant solution VS doing the same work in the middleware VS what DRJ does in its base view class. At least for what I needed, it made more sense to add here.
Basically, I'm just overriding the method 'perform_authentication()' from DRF's code, since I needed to add more things related to the current user in the request. The method just originally call 'request.user'.
class MyGenericViewset(viewsets.GenericViewSet):
def perform_authentication(self, request):
request.user
if request.user and request.user.is_authenticated():
request.my_param1 = 'whatever'
After that in your own views, instead of settings APIView from DRF as a parent class, simply set that class as a parent.
I wasn't quite happy with the solutions out there. Here's a solution that uses some DRF internals to make sure that the correct authentication is applied in the middleware, even if the view has specific permissions classes. It uses the middleware hook process_view which gives us access to the view we're about to hit:
class CustomTenantMiddleware():
def process_view(self, request, view_func, view_args, view_kwargs):
# DRF saves the class of the view function as the .cls property
view_class = view_func.cls
try:
# We need to instantiate the class
view = view_class()
# And give it an action_map. It's not relevant for us, but otherwise it errors.
view.action_map = {}
# Here's our fully formed and authenticated (or not, depending on credentials) request
request = view.initialize_request(request)
except (AttributeError, TypeError):
# Can't initialize the request from this view. Fallback to using default permission classes
request = APIView().initialize_request(request)
# Here the request is fully formed, with the correct permissions depending on the view.
Note that this doesn't avoid having to authenticate twice. DRF will still happily authenticate right afterwards.
I had the same issue and decided to change my design. Instead of using a Middleware I simply monkey-patch rest_framework.views.APIView.
In my case I needed to patch check_permissions but you can patch whatever fits your problem. Have a look at the the source code.
settings.py
INSTALLED_APPS = [
..
'myapp',
]
myapp/patching.py
import sys
from rest_framework.views import APIView as OriginalAPIView
class PatchedAPIView(OriginalAPIView):
def check_permissions(self, request):
print(f"We should do something with user {request.user}"
return OriginalAPIView.check_permissions(self, request)
# We replace the Django REST view with our patched one
sys.modules['rest_framework'].views.APIView = PatchedAPIView
myapp/__init__.py
from .patching import *

Categories