Unittest Flask-App CSRF-token missing after Post-Request - unit-testing

When unittesting my flask-app and sending post-requests with a Testclient I find a form.error that the CSRF token is missing.
I have been to this and to this post, also read the documentation, however I still cant get my Problem solved.
How the CSRF-Protection is created:
class Routes:
app = Flask(__name__)
app.config['SECRET_KEY'] = WebserverConfig().secret_key
CSRFProtect().init_app(__app)
The view to be tested:
#__app.route("/settings", methods=["GET", "POST"])
def settings():
form = SettingsForm()
if request.method == "POST":
if form.validate_on_submit():
Routes.__reqhandler.put_settings(form=form)
return redirect(url_for("settings"))
if form.errors != {}:
Routes.__reqhandler.error_put_settings(form=form)
data = Routes.__prepdata.prep_settings()
return render_template("settings.html", form=form, data=data)
The template which contains the form:
<form method="POST">
{{ form.hidden_tag() }}
<div class="text-center">
<h3>Einstellungen</h3>
</div>
{{ form.intervall.label }}
{{ form.intervall(id="intervall", class="form-control", placeholder=data["intervall"])}}
</form>
The fixture creating the testclient:
#pytest.fixture
def client():
app = Routes().get_app()
app.config["WTF_CSRF_METHODS"] = []
with app.test_client() as client:
yield client
The test:
def test_settings_valid_intervall(client):
res = client.post("settings", data={"intervall": "00:01:00"}, follow_redirects=True)
assert b'value="00:01:00' in res.data
When printing the data (print(res.data) an alert (created with a flash if there are errors in a form) with the info that "The CSRF token is missing" is sent. What am I missing to tell form.validate_on_submit to not check if a valid CSRF-token was passed?

I solved it by not only emptying the list of the WTF_CSRF_METHODS but also disabling CSRF within the fixture that creates the client:
app.config["WTF_CSRF_ENABLED"] = False

Related

Variable 'html' referenced before assigned: UnboundLocalError

This code previously worked and outputed what I wanted on the website, but then this error happened
from django.shortcuts import render
import json
def get_html_content(fplid):
import requests
API_KEY = "eb9f22abb3158b83c5b1b7f03c325c65"
url = 'https://fantasy.premierleague.com/api/entry/{fplid}/event/30/picks/'
payload = {'api_key': API_KEY, 'url': url}
for _ in range(3):
try:
response = requests.get('http://api.scraperapi.com/', params= payload)
if response.status_code in [200, 404]:
break
except requests.exceptions.ConnectionError:
response = ''
#userdata = json.loads(response.text)
return response.text
def home(request):
if 'fplid' in request.GET:
fplid = request.GET.get('fplid')
html = get_html_content(fplid)
return render(request, 'scrape/home.html', {'fpldata': html})
here is my views.py file. I think I assigned html before, but I'm not sure, how is it referenced before it renders. I added scraperapi for many ip addresses, as I thought maybe I was banned from the api. I am unsure what is going on.
<body>
<h1>Enter Your FPL id </h1>
<form method="GET">
<label for="fplid"> </label>
<input type="text", name="fplid", id="fplid"> <br>
<input type="submit" value="Submit" />
</form>
<h3> {{fpldata}}</h3>
</body>
This is a part of the home.html file if it is relevant
When you initially load the page there probably wont'be an initialized ?fplid=xx. When this isn't present the variable is not assigned a value.
You could initialize the variable with html = None or this:
def home(request):
if 'fplid' in request.GET: # <- when this isnt true
fplid = request.GET.get('fplid')
html = get_html_content(fplid)
return render(request, 'scrape/home.html', {'fpldata': html})
return render(request, 'scrape/home.html')

Testing Django view

I'm trying to test the following view
def generate_exercise_edl(request, ex_pk, unit_pk, *args, **kwargs):
ex_instance = Exercises.objects.get(id=ex_pk)
unit_instance = Units.objects.get(id=unit_pk)
unit_edl = UnitEdl.objects.filter(unit=unit_instance)
locations = Locations.objects.all()
unit_edl = list(unit_edl)
print(request)
print(request.POST)
print(request.user)
if request.method == "POST":
for item in unit_edl:
ExerciseEdl.objects.update_or_create(unit=unit_instance, exercise=ex_instance, equipment=item.equipment,
quantity=item.quantity, location=Locations.objects.get(location="Okinawa"))
print(request)
return redirect('exercise-equipment', ex_pk=ex_pk, unit_pk=unit_pk)
else:
messages.error(
request, f'Failed to add/update the {unit_instance.unit_name} edl for {ex_instance.exercise}.')
context = {
'ex_instance': ex_instance,
'unit_instance': unit_instance,
'unit_edl': unit_edl,
'locations': locations,
}
return render(request, 'exercise/exercise_edl.html', context)
This is my test code
def test_generate_edl(self):
unit_edl = UnitEdl.objects.filter(unit=unit.pk)
for edl in unit_edl:
ExerciseEdl.objects.update_or_create(
unit=unit,
exercise=ex,
equipment=edl.equipment,
quantity=edl.quantity,
location=loc
)
response = self.client.post(
f'/exercise/{ex.pk}/edl/{unit.pk}/convert/')
ex_edl = ExerciseEdl.objects.all().count()
self.assertEquals(ex_edl, 2)
self.assertEqual(response.status_code, 302)
This is the URL for the view
path('exercise/<int:ex_pk>/edl/<int:unit_pk>/convert', views.generate_exercise_edl, name='generate-edl'),
And the part of the template that calls my function
<form action="{% url 'generate-edl' ex_pk=ex_instance.id unit_pk=unit_instance.id %}" method="post">
{% csrf_token %}
<input class="btn btn-primary btn-sm mt-2" type="submit" value="Generate EDL">
</form>
My test returns 404, not 302, but the function on the site works, and redirects you.
f'/exercise/{ex.pk}/edl/{unit.pk}/convert/' isn't mapped to any template, it's just the url for the function. In the past my tests have returned a status code of 404 when I wrote the post data incorrectly.
print(request.POST) returns:
<QueryDict: {'csrfmiddlewaretoken':
['ZYT0dgMZqqgmCo2OufdI9B0hIJ5k5qPKcxnkReWPZy0iY9McaBO7MHENjYLzH66O']}>
Which makes sense because I'm not sending any post data, just the csrf token.
What I want to know is, am I on the right track with using 'response = self.client.post(
f'/exercise/{ex.pk}/edl/{unit.pk}/convert/')'?
With my other tests I include the post data in a dictionary along with the URL, but this function doesn't use any, so I just ran a similar function.
Is there a better way to test this? Should I just refactor?
You need to use reverse to build your URL rather than hard coding it. Since you hard coded it, it is getting a 404 since the URL the test tried to post to is incorrect.
I don't know the app_name in your URLs file, you will need to add that to the reverse. For example if it was excercise it would be exercise:generate-edl.
from django.urls import reverse
response = self.client.post(reverse(
'<app_name>:generate-edl',
kwargs={
ex_pk: ex.pk,
unit_pk: unit.pk,
}
))

Django request.method automatically set to GET and not POST

I'd like to setup an LDAP Authentication Backend in Django, and I've already used ldap3 to confirm a bind, with success.
I'm now realising that writing a class for my LDAP Backend with just ldap3 is not so straightforward, and that installing
django_auth_ldap could be another route to explore.
I've tested already some code to create a bind to the LDAP "server", and then perform a simple search. All okay. This method I tested is outside of my Django framework.
When implementing the same method into my Django framework, I encounter an issue when the method gets called. Because of the print statements, I know the view is getting called as exptected, but the part of the code whereby the first "if" statement should be executed, does not get called unless I change from "POST"
to "GET". But then this seems to create the next issue (when I set to "GET", so to force the next lines to be executed), because I'd like that a login page then gets called, where I can then input my LDAP credentials,
ultimately confirming the LDAP connection. Here is my code:
views.py
def login_ldap(request):
LDAP_SERVER = '10.222.4.88'
searchFilter='random'
print (request)
print (LDAP_SERVER)
print ("request.method:{0}".format(request.method))
if request.method == "GET":
print ("if statement executed")
username = request.GET['username']
print ("U:{0}".format(username))
password = request.GET['password']
print ("P:{0}".format(password))
# Define the server and bind_dn
server = Server(LDAP_SERVER, get_info=ALL)
bind_dn = 'cn={0}, ou=Prod, ou=Extern, ou=User, ou=ABC, dc=DEF, dc=com'.format(username)
# Define the Connection
conn = Connection(server, bind_dn, password, auto_bind=True) # Use raise_exceptions=True for exceptions
print ("search: {0}",format(conn.search))
print ("conn: {0}",format(conn))
conn.start_tls() #Session now on a secure channel. See output from following print statement and "tls started"
print ("conn_tls: {0}",format(conn))
d = conn.extend.standard.who_am_i()
print (d)
#print ("Server Info: {0}",format(server.info))
conn.open()
conn.bind()
# The LDAP search base for looking up users.
LDAP_AUTH_SEARCH_BASE = "ou=ABC, dc=DEF, dc=com"
if conn.bind():
conn.search(
search_base=LDAP_AUTH_SEARCH_BASE,
search_filter= '(cn={})'.format(searchFilter), # This is the user being searched for
search_scope=SUBTREE # BASE & LEVEL also possible settings
)
entry = conn.entries[0]
res = conn.bind()
print (res)
return render(request, 'search_page.html', {'entry':entry})
The error message received on my webpage:
MultiValueDictKeyError at /login_ldap/
"'username'"
Request Method: GET
Request URL: http://127.0.0.1:8000/login_ldap/
Django Version: 1.11
Exception Type: MultiValueDictKeyError
Exception Value:
"'username'"
I assume this is related to the GET and no longer the POST method. Why is the request.method being automatically set to GET and not POST when implementing
this in Django? Is this class for the authentication in the correct location, in the views.py file or should there be a separate file for this?
What should be included in the settings.py exactly (related to BACKEND_AUTH)?
EDIT:
views.py
def login_view(request):
if request.POST:
username = request.POST['username']
print ("U:{0}".format(username))
password = request.POST['password']
print ("P:{0}".format(password))
user = authenticate(username=username, password=password)
print (user)
if user is not None:
if user.is_active:
login(request, user)
return redirect('index')
else:
messages.error(request, "User is not active in Database")
else:
print ("Please check your credentials!")
messages.error(request, "Please check your username and password!")
return render(request, 'login.html')
index.html
<div class="row">
<div class="col-lg-3"></div>
<div class="col-lg-6"><H1>This is the public page, Please Login</H1></div>
</div>
urls.py
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^login_ldap/$', login_ldap, name='login_ldap'),
url(r'^login/$', login_view, name='login'),
url(r'^logout/$', logout_view, name='logout'),
url(r'^change_password/$', change_password, name='change_password'),
url(r'^$', index, name='index'),
]
settings.py
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
)
search_page.html
{% load static %}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link href='{% static "login.css" %}' rel="stylesheet">
<div class="wrapper">
<form method='post' action="" class="form-signin">{% csrf_token %}
<h2 class="form-signin-heading">Please login</h2>
<input type="text" class="form-control" name="username" placeholder="Username" required="" autofocus=""/>
<br>
<input type="password" class="form-control" name="password" placeholder="Password" required=""/>
<br>
<button class="btn btn-lg btn-primary btn-block" type="submit">Login</button>
</form>
</div>
You can use get() function to get the data.
if request.method == "GET":
print ("if statement executed")
username = request.GET.get('username')
For more reference you can see here,
MultiValueDictKeyError in Django
And about POST method you may require to import the csr_exempt and use the decorator before your view.
from django.views.decorators.csrf import csrf_exempt
#csrf_exempt
def myView(request):

How to get rid of the csrf validation with EmailMultiAlternatives in DJANGO?

I want to send a html email to some address.
This is part of my code:
views.py
def addNewEvent(request):
try:
eventStatus = EventStatus.objects.filter(event=request.GET["id"])[0]
#try to send the mail
html = get_template('mail.html')
d = Context({ 'username': usuario.name,'title':'Testing mail!!','eventStatusId': str(eventStatus.id)})
html_content = html.render(d)
msg = EmailMultiAlternatives('Testing yo','Skyforger!', 'mymail#gmail.com', [mail2#gmail.com])
msg.attach_alternative(html_content, "text/html")
msg.send()
print('EventStatus id '+str(eventStatus.id))
except Exception, e:
print ('the error %s',(str(e)))
response = getBaseJSON()
response["event_id"]= eventStatus.id
return HttpResponse(json.dumps(response), content_type="application/json")
mail.html
<html>
<body>
<h1>{{title}}</h1>
<h2>Hi {{username}}</h2>
<h3>Message to friend</h3>
<form action="http://localhost:8000/confirmEvent" method="POST">
{% csrf_token %}
<input type="hidden" name="id" value="{{eventStatusId}}">
<textarea name="message_to_friend"></textarea><br>
<input type="submit" value="I'LL BE THERE!!">
</form>
</body>
</html>
The mail is sent OK, but when its form is submitted, this error is displayed:
Forbidden (403)
CSRF verification failed. Request aborted.
I can't find how to solve that error.
I followed many answers like these:
https://stackoverflow.com/a/10388110
Forbidden (403) CSRF verification failed. Request aborted. Even using the {% csrf_token %}
with no success.
How can I send the form inside the html mail avoiding the CSRF error.
You can use the csrf_exempt decorator to disable CSRF protection for a particular view.
from django.views.decorators.csrf import csrf_exempt
#csrf_exempt
def someview():
Its not recommended to disable csrf , but you can try this if you want :)
I replaced render() by render_to_string() with the param context_instance=RequestContext(request) and now this is working.
def addNewEvent(request):
try:
eventStatus = EventStatus.objects.filter(event=request.GET["id"])[0]
#try to send the mail
html = get_template('mail.html')
d = Context()
html_content = render_to_string('mail.html',{ 'username': usuario.name,'title':'Testing mail!!','eventStatusId': str(eventStatus.id)}, context_instance=RequestContext(request))
msg = EmailMultiAlternatives('Testing yo','Skyforger!', 'mymail#gmail.com', [mail2#gmail.com])
msg.attach_alternative(html_content, "text/html")
msg.send()
print('EventStatus id '+str(eventStatus.id))
except Exception, e:
print ('the error %s',(str(e)))
response = getBaseJSON()
response["event_id"]= eventStatus.id
return HttpResponse(json.dumps(response), content_type="application/json")

Django form and angular router ui

I've made a django form to edit values in a module and it is working ok in a single page, not problem so far.
The problem arise when I want to use that form in a state. Currently, I'm using Angular ui- router to display states in a navigation bar. When I click on the "save button" the post action is not being received by the django form.
It's like that I have to route the post method to pass the values first into angular and then into the form(backend).
Any help is appreciated.
Check my code:
views.py
def InstitutionDetailView(request):
client = get_object_or_404(ClientProfile, user=request.user)
institution = Institution.objects.get_or_create(client=client.id)[0]
if request.method == 'POST':
form = EditDetailsForm(request.POST, instance = institution)
if form.is_valid():
form.save()
return render_to_response('account/client/details.html',
{'form': form, 'institution':institution}, context_instance=RequestContext(request))
else:
form = EditDetailsForm(instance = institution)
return render_to_response('account/client/details.html',
{'form': form, 'institution':institution}, context_instance=RequestContext(request))
urls.py
url(r'^user/client/details.html', auth(views.InstitutionDetailView), name="institution_edit_details"),
details.html
<form method="post" action="">
{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Edit">
</form>
app.js
angular.module('account.client', ['ui.router', 'ngTable'])
.config(function ($stateProvider, $interpolateProvider, $urlRouterProvider) {
//allow django templates and singular to co-exist
$interpolateProvider.startSymbol('[[');
$interpolateProvider.endSymbol(']]');
$urlRouterProvider
.when('/institution','/institution/details')
.otherwise('/home');
$stateProvider
.state('home', {
url: '/home',
templateUrl: 'client/dashboard.html'
})
.state('institution', {
url: '/institution',
templateUrl: 'client/institution/'
})
.state('institution.details', {
url: '/details',
templateUrl: 'client/details.html'
});
})