I have a question regarding Django Forms and GET
I have a form to download student scores in CSV format. The fields are name and year so I have a forms.py
StudentDownloadForm(forms.Form):
name=forms.CharField()
year = forms.CharField()
And I want to use this form in the template.html with
context={'student_form' : StudentDownloadForm(),}
<form action ="" method="GET">
{% csrf_token %}{{ student_form|crispy }}
<input type="submit" value="Query"/>
</form>
So my questions are as follows:
If I use the method="GET" then the csrf token is visible in the URL, which is a security issue
Can I then use the method="POST" instead?
Alternatively, can I remove the csrf token in the form?
According to Django documentation (Cross Site Request Forgery protection):
For all incoming requests that are not using HTTP GET, HEAD, OPTIONS
or TRACE, a CSRF cookie must be present, and the ‘csrfmiddlewaretoken’
field must be present and correct. If it isn’t, the user will get a
403 error.
And:
It deliberately ignores GET requests (and other requests that are
defined as ‘safe’ by RFC 2616). These requests ought never to have any
potentially dangerous side effects , and so a CSRF attack with a GET
request ought to be harmless. RFC 2616 defines POST, PUT and DELETE as
‘unsafe’, and all other methods are assumed to be unsafe, for maximum
protection.
So, you can omit CSRF token for GET requiests
Related
I have a Post model that requires a certain category before being added to the database, and I want the category to be generated automatically. Clicking the addPost button takes you to a different page and so the category will be determined by taking a part of the previous page URL.
Is there a way to get the previous page URL as a string?
I have added my AddPost button here.
<aside class="addPost">
<article>
<form action="/Forum/addPost">
<input type="submit" name="submit" value="Add Post"/>
</form>
</article>
</aside>
You can do that by using request.META['HTTP_REFERER'], but it will exist if only your tab previous page was from your website, else there will be no HTTP_REFERER in META dict. So be careful and make sure that you are using .get() notation instead.
# Returns None if user came from another website
request.META.get('HTTP_REFERER')
Note: I gave this answer when Django 1.10 was an actual release. I'm not working with Django anymore, so I can't tell if this applies to Django 2
You can get the referring URL by using request.META.HTTP_REFERER
More info here: https://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpRequest.META
I can't answer #tryingtolearn comment, but for future people, you can use request.META['HTTP_REFERER']
Instead of adding it to your context, then passing it to the template, you can place it in your template directly with:
Return
A much more reliable method would be to explicitly pass the category in the URL of the Add Post button.
You can get the previous url in "views.py" as shown below:
# "views.py"
from django.shortcuts import render
def test(request):
pre_url = request.META.get('HTTP_REFERER') # Here
return render(request, 'test/index.html')
You can also get the previous url in Django Template as shown below:
# "index.html"
{{ request.META.HTTP_REFERER }}
I am building a basic Flask application with a sqlite database.
The feature I am having trouble implementing is for a user to be able to accept or refuse a contact request (like a friend request) from another user.
On the "/contacts" page, you can see all the contact requests you received from other users without problem, but I want to add the option to accept or refuse them and this is where I'm stuck.
A form is dynamically generated by fetching from the database all the contact requests the current user has received and displaying them on the page.
I have tried using two <input type="submit" name="accept/delete" value="id_of_the_request"> tags for each requests, one with the accept option, the other with the delete option, both leading to the same route, but unlike some other input types, the "value" property controls what text appears on the button, so I can't set that to, say, the id of the contact request (which I did in the code further below) because then I see two buttons with a number on my page.
I thought about doing the opposite and setting the name of the tag to the request's id instead, and the value to "delete" or "accept", but then on the server side I wouldn't know what name to get with request.form.get() since the request's id are dynamically generated in the form depending on what's in the database.
I feel like I'm missing some basic knowledge and that it shouldn't be too hard to do that though.
Here is my html code (the template is passed a list of dictionaries (requests) from the database, corresponding to the list of contact requests received by the current user. Each request consists of 3 columns : request_id, user_email, contact_email. request_id is the primary key, user_email is the email of the person who sent the request, while contact_email is the email of the person who received it. ):
<form action="/manage_requests" method="post">
<ul>
{% for request in requests %}
<li>{{request.user_email}} sent you a contact request.</li>
<input type="submit" name="accept" value="{{request.r_id}}">
<input type="submit" name="refuse" value="{{request.r_id}}">
{% endfor %}
</ul>
</form>
Here is my python code to handle accepting or refusing the request :
#app.route("/manage_requests", methods = ["POST"])
#login_required
def manage_requests():
acceptedID = int(request.form.get("accept"))
refusedID = int(request.form.get("refuse"))
## Add the user who sent the request as a contact for both them and us, then delete the request.
if acceptedID :
# fetch the info of the request corresponding id from the database requests table
# get the sender's user_email
# insert the data into the database contacts table for both the sender and the receiver (current user)
# delete the request from the requests table in the database
return redirect("/contacts")
## Delete the request
elif refusedID :
# delete the request from the database requests table
return redirect("/contacts")
This is how I would have done it:
First a route that returns all contact requests like you did:
#app.route("/contacts", methods = ["POST"])
#login_required
def contacts():
# requests_list = Find all the requests for a user
return render_template('contacts.html', requests_list=requests_list)
Then I return in my template all the requests found:
<ul>
{% for request in requests %}
<li>{{request.user_email}} sent you a contact request.</li>
<a href="{{ url_for('manage_requests', request_id=request.r_id, action='accept' )}}">
<input type="submit" name="accept">
</a>
<a href="{{ url_for('manage_requests', request_id=request.r_id, action='refuse' )}}">
<input type="submit" name="refuse">
</a>
{% endfor %}
</ul>
Note how I use the <a></a> tag around the submit button, with the Jinja2 url_for function passed to the href attribute, with the request_id as a parameter and a variable action which takes as the value either accept or refuse:
And finally:
#app.route("/manage_requests", methods = ["POST"])
#login_required
def manage_requests():
action = request.args.get('action')
request_id = int(request.args.get('request_id'))
## Add the user who sent the request as a contact for both them and us, then delete the request.
if action == "accept" :
# fetch the info of the request corresponding id from the database requests table
# get the sender's user_email
# insert the data into the database contacts table for both the sender and the receiver (current user)
# delete the request from the requests table in the database
return redirect("/contacts")
## Delete the request
else :
# delete the request from the database requests table
return redirect("/contacts")
I have an html form, and I would like to insure that all submissions come from my website. I think I have seen people using a key for this (I believe this happens in Django?), and might have some ideas on how to go with that. Is there any standard way to do this in Flask?
Edit:
Now I know I'm talking about CSRF token middleware. Again, is there any standard way of doing this in Flask? How can I store the key on the server side?
In flask you can do CSRF protection using Flask-SeaSurf.There are other methods also but it is straight forward.
To start Just do pip install flask-seasurf and you are ready
import Flask
from flask_seasurf import SeaSurf
app = Flask(__name__)
csrf = SeaSurf(app)
<form method="POST">
...
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
</form>
#csrf.exempt
#app.route('/exempt_view', methods=['POST'])
def exempt_view():
'''This view is exempted from CSRF validation.'''
return 'foobar'
For more information you can visit official website
Please mark this as answer if this solves you problem.
I'm currently writing a simple blog application using Flask-Admin for administrative tasks. I'd like to enable CSRF protection and have followed the instructions on the Advanced Functionality page in the docs.
However, this doesn't seem to prevent my dummy CSRF attacks from succeeding and it is unclear how Flask-Admin follows the CSRF protection implementation outlined in the WTForms documentation.
Regarding my dummy CSRF attacks, my method follows the example for POST requests suggested in the OWASP CSRF article:
<body onload="document.forms[0].submit()">
<form action="http://localhost:5000/admin/blogpost/new?url=%2Fadmin%2Fblogpost%2F" method="POST">
<input type="hidden" name="title" value="Evil title"/>
<input type="hidden" name="content" value="Evil content"/>
<input type="submit" value="Save"/>
</form>
where the action address is the new post creation page that draws from the create.html template of Flask-Admin.
When I load the malicious form from a separate origin (localhost:3000) and submit it on document load, I find that the request goes through despite the form being configured with the Flask-Admin SecureForm setup linked above.
I've tried consulting the Flask-Admin source code, but couldn't find the WTForms csrf_token in the relevant templates, though the SecureForm includes the CSRF settings as expected.
Does Flask-Admin diverge from the documented WTForms implementation of CSRF protection? If so, how?
More practically speaking, is there any additional configuration beyond the use of Flask-Admin's SecureForm in my ModelView-based classes necessary to enable CSRF protection?
Note also that the technique described in this answer also worked for me in my attempts to use exposed admin URLs cross-site (again from localhost:3000) to perform actions requiring authentication.
This leads me to ask the follow-up: is it possible to implement CSRF on non-form Flask-Admin pages, i.e. for GET requests, to protect exposed admin URLs used for operations such as logout or account deletion operations?
What seemed like a simple bug - a form submission that won't go through due to a "CSRF token missing" error - has turned into a day of hair pulling. I have gone through every SO article related to Flask or Flask-WTF and missing CSRF tokens, and nothing seems to be helping.
Here are the details:
Following Martijin's guidelines to an earlier question:
The Flask-WTF CSRF infrastructure rejects a token if:
1) the token is missing. Not the case here, you can see the token in the form.
The token is definitely present in my form, and being POST'ed successfully
2) it is too old (default expiration is set to 3600 seconds, or an hour).
Set the TIME_LIMIT attribute on forms to override this. Probably not the
case here.
Also OK for me - the token is well within the default expiration time
3) if no 'csrf_token' key is found in the current session. You can
apparently see the session token, so that's out too.
In my case, session['csrf_token'] is properly set and seen by Flask
4) If the HMAC signature doesn't match; the signature is based on the
random value set in the session under the 'csrf_token' key, the
server-side secret, and the expiry timestamp in the token.
This is my problem. The HMAC comparison between the submitted form's CSRF and the session CSRF fails. And yet I don't know how to solve it. I've been desperate enough (as with the other questioner) to dig into Flask-WTF code and set debugging messages to find out what's going on. As best I can tell, it's working like this:
1) generate_csrf_token() in "form.py" (Flask-WTF) wants to generates a CSRF token. So it calls:
2) generate_csrf() in "csrf.py". That function generates a new session['csrf_token'] if one does not exist. In my case, this always happens - although other session variables appear to persist between requests, my debugging shows that I never have a 'csrf_token' in my session at the start of a request. Is this normal?
3) The generated token is returned and presumably incorporated into the form variable when I render hidden fields on the template. (again, debugging shows that this token is present in the form and properly submitted and received)
4) Next, the form is submitted.
5) Now, validate_csrf in csrf.py is called. But since another request has taken place, and generate_csrf() has generated a new session CSRF token, the two timestamps for the two tokens (in session and from the form) will not match. And since the CSRF is made up in part by expiration dates, therefore validation fails.
I suspect the problem is in step #2, where a new token is being generated for every request. But I have no clue why other variables in my session are persisting from request to request, but not "csrf_token".
There is no weirdness going on with SECRET_KEY or WTF_CSRF_SECRET_KEY either (they are properly set).
Anyone have any ideas?
I figured it out. It appears to be a cookie/session limit (which probably beyond Flask's control) and a silent discarding of session variables when the limit is hit (which seems more like a bug).
Here's an example:
templates/hello.html
<p>{{ message|safe }}</p>
<form name="loginform" method="POST">
{{ form.hidden_tag() }}
{{ form.submit_button() }}
</form>
myapp.py
from flask import Flask, make_response, render_template, session
from flask_restful import Resource, Api
from flask_wtf import csrf, Form
from wtforms import SubmitField
app = Flask(__name__)
app.secret_key = '5accdb11b2c10a78d7c92c5fa102ea77fcd50c2058b00f6e'
api = Api(app)
num_elements_to_generate = 500
class HelloForm(Form):
submit_button = SubmitField('Submit This Form')
class Hello(Resource):
def check_session(self):
if session.get('big'):
message = "session['big'] contains {} elements<br>".format(len(session['big']))
else:
message = "There is no session['big'] set<br>"
message += "session['secret'] is {}<br>".format(session.get('secret'))
message += "session['csrf_token'] is {}<br>".format(session.get('csrf_token'))
return message
def get(self):
myform = HelloForm()
session['big'] = list(range(num_elements_to_generate))
session['secret'] = "A secret phrase!"
csrf.generate_csrf()
message = self.check_session()
return make_response(render_template("hello.html", message=message, form=myform), 200, {'Content-Type': 'text/html'})
def post(self):
csrf.generate_csrf()
message = self.check_session()
return make_response("<p>This is the POST result page</p>" + message, 200, {'Content-Type': 'text/html'})
api.add_resource(Hello, '/')
if __name__ == '__main__':
app.run(debug=True)
Run this with num_elements_to_generate set to 500 and you'll get something like this:
session['big'] contains 500 elements
session['secret'] is 'A secret phrase!'
session['csrf_token'] is a6acb57eb6e62876a9b1e808aa1302d40b44b945
and a "Submit This Form" button. Click the button, and you'll get:
This is the POST result page
session['big'] contains 500 elements
session['secret'] is 'A secret phrase!'
session['csrf_token'] is a6acb57eb6e62876a9b1e808aa1302d40b44b945
All well and good. But now change num_elements_to_generate to 3000, clear your cookies, rerun the app and access the page. You'll get something like:
session['big'] contains 3000 elements
session['secret'] is 'A secret phrase!'
session['csrf_token'] is 709b239857fd68a4649deb864868897f0dc0a8fd
and a "Submit This Form" button. Click the button, and this time you'll get:
This is the POST result page
There is no session['big'] set
session['secret'] is 'None'
session['csrf_token'] is 13553dce0fbe938cc958a3653b85f98722525465
3,000 digits stored in the session variable is too much, so the session variables do not persist between requests. Interestingly they DO exist in the session on the first page (no matter how many elements you generate), but they will not survive to the next request. And Flask-WTF, since it does not see a csrf_token in the session when the form is posted, generates a new one. If this was a form validation step, the CSRF validation would fail.
This seems to be a known Flask (or Werkzeug) bug, with a pull request here. I'm not sure why Flask isn't generating a warning here - unless it is somehow technically unfeasible, it's an unexpected and unpleasant surprise that it is silently failing to keep the session variables when the cookie is too big.
Instead of going through the long process mentioned above just add the following jinja code {{ form.csrf_token }} to the html side of the form and that should take care of the "CSRF token missing" error. So on the HTML side it would look something like this:
<form action="{{url_for('signup')}}" method="POST">
{{ form.csrf_token }}
<fieldset class="name">
{{ form.name.label}}
{{ form.name(placeholder='John Doe')}}
</fieldset>
.
.
.
{{ form.submit()}}