How to detect redirection in Django view - python

I have a view that returns a redirection.
return redirect(reverse('my-view-name'))
Is it a way to detect redirection inside a view method?
I've searched in request object attributes but couldn't find any one that indicates redirection.

A redirection simply means you return a HttpResponseRedirect object [Django-doc] with status code 302 and a header Location that contains the location to which you redirect. There is nothing special about that response, and the target view is not triggered by the redirect response itself.
The browser that receives such response, can then thus act by visiting that link, and then it will thus trigger the view to which you redirected. But a browser does not per se does that (you can usually alter some settings to let your browser (not) follow such redirects).
You can thus for example implement your own middleware that does something with such redirect:
def redirect_processing_middleware(get_response):
def middleware(request):
response = get_response(request)
if 300 <= response.status_code < 400:
# the response is a redirect
# ...
pass
return middleware
An alternative is to add a watchdog on the redirect(..) call, but that can be circumvented, by simply creating a HttpResponse for example, and manually alter that response to transform it into a redirect response.
You can do some static code analyses (automatically or manually). But due to the dynamic nature of Python, it is impossible to know for sure what views will trigger redirections: one can obfuscate the redirect call, by using a proxy function, etc., or dynamically alter/monkey patch code such that redirections are taking place without (explicitly) calling redirect(..).

Related

In Django, can I use context in redirect?

I am trying to use redirect with context but I get a NoReverseMatch exception. Code is as follows:
return redirect(reverse('td:success', kwargs={ 'data': my_data }))
When I use render, all goes well and I am able to access the context in the template. But I want redirect instead.
enter image description here
But I want redirect instead.
You can't. The redirect(…) function [Django-doc] simply returns a HTTP response that contains the URL to visit next. The browser will then visit that URL, it is thus the browser that makes the next HTTP request, and the previous request and response are "destroyed". You thus need the database, cookies, session variables, URL parameters, etc. to pass data between the two requests.
If you use a URL parameter, you can thus add data to this. In that case, your code snippet can be simplified to:
return redirect('td:success', data=my_data)
there is no need to work with reverse(…) since redirect(…) will call reverse(…) internally.

Django - Changing the url to redirect from a middleware on the fly

I'm building a SSO login meant to be used from links send by emails.
Each link should auto-connect the user (SSO), and be clickable multiple times (they have a TTL, which depends on the email)
It works fine, but I'm concerned about the end-user sharing his url on social networks (basically copy/pasting the url, which contains the SSO token), allowing anyone following the link to be logged in automatically.
My first attempt was to try to remove the GET SSO_TOKEN parameter, from my SSOMiddleware, as follow:
if remove_token_middleware:
request.GET._mutable = True # GET is not mutable by default, we force it
# Remove the token from the url to avoid displaying it to the client (avoids sharing sso token when copy/pasting url)
del request.GET[SSO_TOKEN_PARAM]
request.GET._mutable = False # Restore default mutability
return login(request, user) if service.get("auto_auth") else None
Basically, my thought was that since the SSO_TOKEN is in the request.get object, removing it from it would eventually change the url where the user gets redirected
In my controller, here is how the user gets "redirected" (using render)
return render(request, 'campagne_emprunt/liste_offres_prets.html', locals())
When using render, there is no redirection, and the SSO token is still visible in the URL (in the browser address bar).
Is there a way to somehow tell Django to change the destination url, on the fly?
So in order to redirect, there is a builtin redirect function of Django that you use instead of rendering I think .. give it a shot,
from django.shortcuts import redirect
def redirect_view(request):
response = redirect('/redirect-success/')
return response
and for reference-
https://realpython.com/django-redirects/

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

Looking for Django equivalent of Flask's request.get_data() (for Slack request verification with raw request body) [duplicate]

Since Django 1.5 raw post data is accessible via request.body.
In my application I sometimes get data send via a form and sometimes raw data (json for example).
Is there any way to write a function like this that does not fail?
def get_post_var(request, name):
result = request.POST.get(name)
if result:
return result
post_body = dict(urlparse.parse_qsl(request.body))
result = post_body.get(name)
if result:
return result
return None
Use request.data instead of request.body.
request.data does not read the data stream again.
The error You cannot access body after reading from request's data stream will be triggered on a request if (1) that request method is POST, (2) that request's POST dictionary is accessed in middleware, in either process_request or process_view and (3) within the view function, request.body is accessed. It is on (3) that the error will be raised, even though the real cause of the bug is (2).
In order to resolve the error, you need to examine your middleware for where it accesses request.POST and modify it such that it doesn't access request.POST anymore.
The Django docs say that middleware should not access request.POST, and this is one consequence of ignoring that recommendation.
Also check out this Django ticket on the issue, which includes the note:
[M]iddleware that hits request.POST should (usually) be considered a
bug. It means that the view will be unable to set any custom upload
handlers, perform custom parsing of the request body, or enforce
permission checks prior to file uploads being accepted.
Adding to Adam Easterling's answer it is worth noting that Django itself 'violates' the hint of not using request.POST in middleware:
The CsrfViewMiddleware class can be considered an exception, as it
provides the csrf_exempt() and csrf_protect() decorators which allow
views to explicitly control at what point the CSRF validation should
occur.
Which does not sanitilize the violation IMO
For those interested to know, I faced this issue:
You cannot access body after reading from request's data stream
when I added 'oauth2_provider.contrib.rest_framework.OAuth2Authentication'
in the "REST_FRAMEWORK" like so in the settings.py:
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES': (
...
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
...
),
Of course disabling this will work but not a workaround I would be proud of.
I was able to read my request.POST after putting #csrf_exempt before my view function. Because CSRF middleware accesses POST data.
I found a trick, using for my middleware,
request._read_started = False
after doing that, ready body again and it works.
For those with the same error who are not readying the body or POST, I had this same error when I used this line of code in a process_view middleware::
event = request.event if 'event' in request else None
Solved by settings request.event = None at the top of the function so I could then use:
event = request.event

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'.)

Categories