Sample code:
#app.route('/delete-update/<int:post_id>')
#login_required
def delete_update(post_id):
try:
p = Post.query.filter(Post.id == post_id).delete()
db.session.commit()
flash('Post successfully deleted')
except:
db.session.rollback()
return redirect(url_for('index'))
I can delete a post using this route by a button click, But how to prevent deleting a post by requesting the URL
localhost:5000/delete-update/1
You should use the methods attribute after your route definition.
#app.route('/delete-update', methods=['POST'])
if request.method == 'POST':
#do your stuff
You can also use any default http method like PUT, DELETE, PATCH.
Be aware that with that change, if you do a request with method GET to:
http://localhost:5000/delete-update/1
you will receive a 405response with message method not allowed.
That's because our route does not handle GET anymore
Also doing that you should change the button handle to send a request to that route with method POST.
Related
I want to update and delete post and it is happening but now I want users to Update and delete a post only created by them. Strating from delete this is my delete function
def delete_post(request , id):
post=Post.objects.get(pk=id)
if request.user==post.user: '''I think this if is not true even when the post is created by the same user who is requesting to delete it.'''
post.delete()
print("ok")
return redirect("home")
Now when click on delete post it returns to home page but the post remains same.it doesn't delete the post.
Try this in more cleaner way
#login_required
def delete_post(request , id):
try:
Post.objects.filter(pk=id, user=request.user).delete()
except Exception:
raise NotFoundError('The post you are trying to delte not found')
return redirect("home")
No one can delete another user created post this way
If request.user is not the post.owner, then it will not delete the post, but it will make a redirect to the home view.
Normally one should not create, update or remove items with a GET request, since GET requests are supposed to be safe and only retrieve data. You thus should limit this view to a POST request (or perhaps a POST or DELETE request).
You can work with the get_object_or_404(…) function [Django-doc] to return a HTTP 404 response in case the object does not exist, or is not owned by the current user:
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_POST
#login_required
#require_POST
def delete_post(request , id):
post = get_object_or_404(Post, pk=id, owner=request.user).delete()
return redirect('home')
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].
Note: You can limit views to POST requests with the
#require_POST decorator [Django-doc].
Setup:
Flask web app.
User navigates to one of multiple pages [e.g. game page (localhost:5000/game) or players page (localhost:5000/players)] through a post request with GAME_ID in the form. GAME_ID is used to fetch additional details and render the page.
Objective:
When an unauthenticated users makes a post request to /game, redirect them to the /login page but retain the endpoint (game) and the GAME_ID in the session, so that on successful login, I can send them back to the page they were accessing. My approach was to make a 307 redirect.
Approach (based on this and this):
In application.py
#login_manager.unauthorized_handler
def intercept_unauthorized():
<Set NEXT_PAGE_FOR_REDIRECT and GAME_ID_FOR_REDIRECT into the session>
return redirect(url_for('login'))
#application.route("/login", methods=['GET', 'POST'])
def login():
#User sees the login page
if request.method=='GET':
<show the login page>
#User has submitted username and password on the login page
elif request.method=='POST':
<authentication code here>
if validUser:
#Get the page the user was trying to access e.g. 'game'
nextPage = session.get('NEXT_PAGE_FOR_REDIRECT')
session.pop('NEXT_PAGE_FOR_REDIRECT')
gameID = session.get('GAME_ID_FOR_REDIRECT')
session.pop('GAME_ID_FOR_REDIRECT')
return redirect(url_for(next_page, gameID=gameID), code=307)
#application.route("/game", methods=['POST'])
#login_required
def game():
if 'gameID' in request.form:
render_template('game.html', gameID=request.form['gameID'])
Issue:
I am able to redirect to /game where request.method is POST. But, the data in request.form is the data submitted from the login page submit i.e. username and password.
I am able to access the gameID I set in the url_for line, but it is sent in request.args (and is visible in the browser's address bar) instead of through request.form.
Questions:
Is my approach of using a redirect correct?
How can I modify the form being sent to /game so that I can add gameID to it?
From: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307
HTTP 307 Temporary Redirect redirect status response code indicates
that the resource requested has been temporarily moved to the URL
given by the Location headers.
The method and the body of the original request are reused to perform
the redirected request. In the cases where you want the method used to
be changed to GET, use 303 See Other instead. This is useful when you
want to give an answer to a PUT method that is not the uploaded
resources, but a confirmation message (like "You successfully uploaded
XYZ").
The only difference between 307 and 302 is that 307 guarantees that
the method and the body will not be changed when the redirected
request is made.
So, the original request.form is retained. You could just use the session to display the hidden ID, like:
#application.route("/game", methods=['POST'])
#login_required
def game():
gameID = session.get('GAME_ID_FOR_REDIRECT')
session.pop('GAME_ID_FOR_REDIRECT')
render_template('game.html', gameID=gameID )
Of course, you'd have to remove the pop() from login()
I'm trying to develop a REST API with DRF that uses TokenAuthentication. This will be used in an android app.
I was able to authenticate a user and retrieve it's token. The problem I'm having now is with the following view:
#csrf_exempt
def foo(request):
if request.method == 'GET':
if request.user.is_authenticated():
...
do stuff
...
return HttpResponse(data, "application/json")
else:
return HttpResponse(status=401)
Basically the user should be authenticated in order to receive the data, otherwise, he will receive a 401 response.
I'm making a GET request to the proper URL with the following parameters in the Header:
content-type : application/json
authorization : Token <user token>
Which is basically what I'm doing for other Viewsets (this is not a Viewset) I have - and it works.
In this case, it's always sending the HTTP response with 401 code (user isn't authenticated).
I can't figure out if the problem is with the Header values I'm passing or if this is not the proper way to check if the user is authenticated.
Edit: if I do: "print request.user" i get AnonymousUser
Thanks!
Solved
As suggested by "ABDUL NIYAS P M" I used the APIView
Basically, I just added the #api_view(['GET']) decorator to the View.
#csrf_exempt
#api_view(['GET'])
#permission_classes((IsAuthenticated, ))
def foo(request):
if request.method == 'GET':
...
An easier way to do this is by checking if the user session is existing or not.
When DRF creates a token, it also creates the session cookie.
return HttpResponse(json.dumps({"is_authenticated": True if request.session.get('_auth_user_id', 0) else False}),
content_type='application/json')
I am new to Flask framework and am playing around with it to learn it better. I am following this tutorial along my way.
As per the User Authentication tutorial in the series, I am stuck with the below:
In the tutorial, when the user logs out by hitting the /logout route, the first thing that happens is :
session.pop('logged_in', None)
Now as per the video mentioned above, the moment user hits the /logout route the cookie also gets deleted from the browser.
Now 2 questions here:
In my case, with the exact same setup as the tutorial, although the session might be getting invalidated from the server end, the cookie does NOT get deleted/changed in any way from the browser after the /logout route is hit. Is there something wrong that am doing?
session.pop(...) => how/why exactly will it delete something from the front end, the browser. It can only control things on the server, isn't it ?
For your reference below is my code (taken from the tutorial itself)
# import the Flask class from the flask module
from flask import Flask, render_template, redirect, url_for, request, session, flash
# create the application object
app = Flask(__name__)
app.secret_key = 'my precious'
# use decorators to link the function to a url
#app.route('/')
def home():
return "Hello, World!" # return a string
#return render_template(index.html)
#app.route('/welcome')
def welcome():
return render_template('welcome.html') # render a template
# route for handling the login page logic
#app.route('/login', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
if request.form['username'] != 'admin' or request.form['password'] != 'admin':
error = 'Invalid Credentials. Please try again.'
else:
session['logged_in'] = True
flash('You were just logged in')
return redirect(url_for('home'))
return render_template('login.html', error=error)
#app.route('/logout')
def logout():
session.pop('logged_in', None)
flash('You were just logged out')
return redirect(url_for('welcome'))
# start the server with the 'run()' method
if __name__ == '__main__':
app.run(debug=True)
First of all session and cookie is not the same. Session is more like unique id posted to you browser and something like a key for the dictionary for you backend. So most of the time, when you change session(not session id), you just modify backend part(add or delete values in backend dictionary by that key). Not browser's cookie.
You understood all correct. When you pop "logged in" from session server will remember that this browser will not logged_in any more.
So cookie used here just to identify the client browser. That is it.
You can set the expiry time of cookie to 0, this will make it invalid
#app.route('/logout')
def logout():
session.pop('token', None)
message = 'You were logged out'
resp = app.make_response(render_template('login.html', message=message))
resp.set_cookie('token', expires=0)
return resp
To remove the cookie you want to remove, you need to set the cookie's max-age to 0, and send it back to the user's browser. You can achieve this by doing this:
#app.route('/logout')
def logout():
resp = make_response(render_template('login.html', message="You are logged out."))
resp.delete_cookie('token')
return resp
the delete_cookie method is a shorthand for calling set_cookie with the cookie's name, an empty string for the value, and expires=0. The method sets the max-age of the cookie to 0, which means it is immediately expired.
In some older browsers you may find that they don't handle the Expires attribute correctly, so using delete_cookie might be a safer choice.
It also looks more clean IMHO.
I'm using Pyres workers to do some processing of data users enter in a form. Their processing is done by a view on my form, which I make a POST request to, with data including the data to process and a CSRF middleware token for the user. My issue is that this is apparently not enough, as Django still rejects my request with a 403 forbidden.
Relevant code:
Form handler:
def handler(request):
if(request.method == "POST"):
if(request.POST.__contains__("taskdata")):
#valid post of the form
taskdata = escape(request.POST.get("taskdata",""))
t = TaskData(data=taskdata, time_added=timezone.now(), token=request.POST.get("csrfmiddlewaretoken",""))
t.save()
r = ResQ(server="127.0.0.1:6379")
r.enqueue(TaskData, t.id)
return HttpResponse(t.id)
else:
#invalid post of the form
raise Http404
else:
raise Http404
Pyres worker job:
#staticmethod
def perform(taskData_id):
#Get the taskData from this id, test it for tasky stuff
task_data = TaskData.objects.get(pk=taskData_id)
post_data = [('id',task_data.id),('data',task_data.data), ('csrfmiddlewaretoken',task_data.token)] # a sequence of two element tuples
result = urllib2.urlopen('http://127.0.0.1:8000/tasks/nlp/process/', urllib.urlencode(post_data))
content = result.read()
return
View being posted to by that job:
def process(request):
if(request.method == "POST"):
return HttpResponse("HEY, it works!")
if(request.POST.__contains__("data") and request.POST.__contains__("id")):
#valid post to the form by the model
#taskdata = escape(request.POST.get("taskdata",""))
#data = get_times(taskdata)
return HttpResponse("Hey from process!")
#return HttpResponse(json.dumps(data))
else:
#invalid post of the form
raise Http404
else:
raise Http404
What I'm basically trying to do is save some raw data at form submission, along with the CSRF token for it. The workers then send that data + token to a processing view.
Unfortunately, posting the token doesn't seem to be enough.
Does anybody know what the csrf protection actually looks for, and how I can make my Pyres workers compliant?
(Suggested tag: pyres)
I think I see the problem.
The way Django's CSRF protection works is by generating a nonce, then setting a cookie to the value of the nonce, and ensuring the csrfmiddlewaretoken POST value matches the value of the cookie. The rationale is that it makes it a stateless system, which works without any persistent session data.
The problem is that the request you make in the Pyres worker job...
result = urllib2.urlopen('http://127.0.0.1:8000/tasks/nlp/process/',
urllib.urlencode(post_data))
...is coming from the server, not the client, so it won't have the cookie set.
Assuming the /tasks/nlp/process/ URL is protected such that it can only be accessed by the server, then it's probably simplest to make the process() view exempt from CSRF checking with...
#csrf_exempt
def process(request):
...
...otherwise you'll have to manually grab the cookie value in the handler() view, and pass it on to the Pyres worker job.
Update
To ensure the process() method can only be called by the server, one simple way would be to check the request object with something like...
#csrf_exempt
def process(request):
if request.META['REMOTE_ADDR'] != '127.0.0.1':
# Return some error response here.
# 403 is traditional for access denied, but I prefer sending 404
# so 'hackers' can't infer the existence of any 'hidden' URLs
# from the response code
raise Http404
# Now do the thing
....
...although there may be some built-in decorator or somesuch to do this for you.