I have a simple form:
{% block content %}
<p> Upload invoices </p>
<form method="post" action="{% url 'upload_thing ' %}" enctype="multipart/form-data">
{% csrf_token %}
<input type="file" name="invoice_thing">
<button type="submit">Upload</button>
</form>
I have a view
#require_POST
#transaction.atomic
def upload_thing(request):
....
How do I make sure that the no one can hit the post endpoint via curl or postman?
I want the end point to be accessible only by hitting the form button. The only people who can do this are admin users.
How do I accomplish this?
To be honest, you shouldn't prevent curl or wget requests from django application. It can be done from a reverse proxy server, for example in NGINX you can put the following configuration:
if ($http_user_agent ~* (wget|curl) ) {
return 403;
}
Still, its not a proper protection, User-Agent information can be spoofed. More information can be found in this serverfault answer.
If you want to prevent people accessing this page who aren't admin, then you can simply put a restriction on the view like this:
from django.core.exceptions import PermissionDenied
from django.contrib.auth.decorators import login_required
#login_required
#require_POST
#transaction.atomic
def upload_thing(request):
if not request.user.is_superuser:
raise PermissionDenied()
# rest of the code
The right way to do this in python is via decorators.
from django.contrib.auth.decorators import login_required, user_passes_test
#user_passes_test(lambda user: user.is_superuser)
def upload_thing(request):
In this way you restrict the method and not the html page.
So only logged admin can use upload_thing (from curl, postman, or html form).
Related
If I'm on foo.html (any view/template) and the session times out, and then I click on a link to bar.html (any other view), I'm taken to the login page. After successful authentication, I am redirected to bar.html. I want it to always redirect to home.html. My settings.py has LOGIN_REDIRECT_URL = 'home'.
Users are prompted to login after the sessions expires because all my views require the user to be logged in. In CBV I use:
#method_decorator([login_required], name='dispatch')
class QuestionListView(PermissionRequiredMixin, ListView):
In functional views I have a decorator:
# login_required
def home(request):
I am using the django auth and have not overridden the login view. My login.html file contains:
<form method="post">
{% csrf_token %}
{{ form|crispy }}
<a class="button secondaryAction" href="{% url 'password_reset' %}">Forgot Password?</a>
<button type="submit" class="btn btn-secondary mt-2 pl-4">Log In</button>
</form>
Looking at the django code, I think the answer lies in django.contrib.auth.views:
class RedirectURLMixin:
next_page = None
redirect_field_name = REDIRECT_FIELD_NAME
success_url_allowed_hosts = set()
def get_success_url(self):
return self.get_redirect_url() or self.get_default_redirect_url()
def get_redirect_url(self):
"""Return the user-originating redirect URL if it's safe."""
redirect_to = self.request.POST.get(
self.redirect_field_name, self.request.GET.get(self.redirect_field_name)
)
url_is_safe = url_has_allowed_host_and_scheme(
url=redirect_to,
allowed_hosts=self.get_success_url_allowed_hosts(),
require_https=self.request.is_secure(),
)
return redirect_to if url_is_safe else ""
def get_success_url_allowed_hosts(self):
return {self.request.get_host(), *self.success_url_allowed_hosts}
def get_default_redirect_url(self):
"""Return the default redirect URL."""
if self.next_page:
return resolve_url(self.next_page)
raise ImproperlyConfigured("No URL to redirect to. Provide a next_page.")
My guess is that def get_default_redirect is happening because I click on a link (the next_page) which triggers the login (because of the session time out).
The only reason I want to have all logins go to the home page is because I'm temporarily putting an announcement banner on the home page. It's possible for a user to not see the banner if they never go to the home page by simply logging in using the method described here.
I have a bit of an issue with CSRF verification in my Django app. I have two other {% csrf_token %} tags in my app, in two different HTML templates. These work fine, and always have. When attempting to add a third in another new template though, I receive the '403 Forbidden' page. I've copied the style of the two working 'post' commands exactly, but for some reason this one will not work. Any suggestions?
The 'post' form contains a single select/drop-down object, and a submit button. Clicking the button should direct to a view to process the posted data, written again just like the first 2, but it throws the 403 error instead, with the reason for failure being 'CSRF token missing or incorrect'. I'm running Django 1.4.22 also, with User authentication enabled. Here is the not-working view:
from django.shortcuts import render_to_response, get_object_or_404
from django.template import RequestContext
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required
...Other Views...
#login_required
def Process(request, id):
...
try:
choice = request.POST['choice']
except(KeyError, Item.DoesNotExist):
return render_to_response('app/home.html', context_instance=RequestContext(request))
else:
...Process posted data...
return HttpResponseRedirect(reverse('app.views.display', args=()))
Here is the working view:
#Same includes/imports as earlier, in same file
#login_required
def Submit(request, id, id_2, data):
try:
new_data = request.POST[data.name]
except(KeyError, DataPoint.DoesNotExist):
return render_to_response('app/home.html', context_instance=RequestContext(request))
else:
...Process new data...
return HttpResponseRedirect(reverse('app.views.Data_View', args=()))
And here is the HTML file:
<!DOCTYPE html>
...
<table>
<tr><td>
<form action="/app/page/to/redirect/to/" method="post">
{% csrf_token %}
<select name="choice">
<option name="yes" value="yes" selected>Yes</option>
<option name="no" value="no">No</option>
</select>
</form></td></tr>...</table>
This might be a duplicate but i tried using RequestContext from other answers but it didnt work for me
checkout_test.html:
<form action="" method="POST"> {% csrf_token %}
<script src="https://checkout.stripe.com/checkout.js" class="stripe-button"
data-key="pk_test_37uDrOYvvyyJSLoV0ziJcYyl"
data-amount="2000"
data-name="Demo Site"
data-description="2 widgets ($20.00)"
data-image="/128x128.png"
data-locale="auto">
</script>
</form>
views.py
def user_review_list(request, username=None, errmsg=None):
return render(request, 'checkout_test.html', {})
so in user_review_list.html, there is a button provided by stripe
when i fill out info and click the button, it raises error:
CSRF token missing or incorrect.
How can i fix this?
I've already tried changing render to render_to_response with RequestContext but that didnt work
You cannot pass your CSRF cookie to Stripe and back. One workaround is to use the #csrf_exempt decorator:
from django.views.decorators.csrf import csrf_exempt
#csrf_exempt
def user_review_list(request, username=None, errmsg=None):
...
I wrote a function that allows the user to delete his article on a blog website. The problem is, if he plays a little with the url, he can access to another article and delete it.
What is the common strategy to avoid such cases with django?
here are the codes I wrote for the fonction:
views.py
def delete_article(request, id):
deleted = False
logged_user = get_logged_user_from_request(request) #that line allow to ensure that the user is connected. I use the session to achieve that instead of extending the User model
offer = get_object_or_404(Offer, id=id)
if request.method == 'POST':
offer.delete()
deleted = True
return render(request, 'offers/delete_article.html', locals())
urls.py
urlpatterns = patterns('article.views',
url(r'^send_article$', 'send_article', name='send_article'),
url(r'^my_articles$', 'show_my_articles', name='my_articles'),
url(r'^article/(?P<id>\d+)$', 'read', name='read'),
url(r'^articles$', 'show_articles', name='articles'),
url(r'^search_article$', 'search', name='search'),
url(r'^delete_article/(?P<id>\d+)$', 'delete_offer', name='delete_offer'),
)
delete_article.html
{% if not deleted %}
Hey, are you sure you want to delete {{ article.title }}?
<form method="POST">
{% csrf_token %}
<button type="submit" class="deleting_offer_button">delete</button>
</form>
{% elif deleted %}
<p>the article was successfully deleted</p>
get back to the homepage<br />
{% endif %}
As you can see, if the user change the numer of the id in the url, he can delete other article when he is directed to the confirmation of deleting page.
What webmasters are doing to ensure users cannot interfere with objects of other users?
HttpResponseForbidden can be used here which uses a 403 status code. A 403 response generally used when authentication was provided, but the authenticated user is not permitted to perform the requested operation.
Assuming you have author as an foreign key in Offer model, you can change your views like this:
In your views.py you have to import :
from django.http import HttpResponseForbidden
And then in your delete_article method use this code
offer = get_object_or_404(Offer, id=id)
if offer.author != request.user:
return HttpResponseForbidden()
When you get the article/offer. Make sure that the owner of that article is the authenticated user.
I'm not sure what your models look like but it would be something like
offer = get_object_or_404(Offer, id=id, author=logged_user)
This way if they don't own the article, it will 404
I have a view defined for a url 'site/main/'. I would like to be able to have (unauthenticated) users redirected to the default '/admin/' page for login, then redirected to the '/main/' page after successful login. I followed the django documentation, but I must be missing something as I am unable to get this to work.
My view looks like:
def main(request):
if not request.user.is_authenticated():
return HttpResponseRedirect('admin/?next=%s' % request.path)
else:
I get an error:
Page not found (404)
Request Method: GET
Request URL:http://sitename:8080/main/admin/?next=/main/
Any help is greatly appreciated !
You're missing an initial / in the URL: /admin/?next=...
However this still won't work, as the admin URL doesn't know anything about the next parameter. That's only for the actual login views. With your code, the user will be logged into the admin but will not be redirected back to your page.
You should build a login template and wire it up to the built-in login views. Then instead of checking is_authenticated in the view, you should just use the login_required decorator.
#login_required
def main(request):
...
Your request.path shouldn't be /main/. Try it without the first.
If you want to redirect to admin for login for specific view, and then to redirect back to the view url after successful login you only need to do two things:
Add LOGIN_URL to settings.py inside your django project module:
...
LOGIN_URL = '/admin/login/'
Add #login_required as decorator to your view function inside views.py:
from django.contrib.auth.decorators import login_required
...
#login_required
def main(request):
Once you set LOGIN_URL = '/admin/login/' you can use #login_required on whatever view in entire django project and it will redirect to admin for login and after successful login will redirect back to the view url.
Also now you don't need to use is_authenticated any more inside of a view as Daniel Roseman already said.
The good thing is that now you also don't need to build a login template and wire it up to the built-in login views.
What is also good with this approach is the you have flexibility to easily add or remove this kind of authentication to whatever view you want.
urls.py:
url('^', include('django.contrib.auth.urls')),
registration/login.html:
<h3>Login foo</h3>
<form method="post" action="">
{% csrf_token %}
{{form.as_p}}
<input type="submit" value="Login">
</form>
views.py
def only_for_users(request):
if not request.user.is_authenticated():
return HttpResponseRedirect('/login/?next=%s' % request.path)
// fetch some really interesting data
env=(django.get_version(),settings.BASE_DIR,sys.version)
envMod=collections.OrderedDict(sorted(sys.modules.items()))
return render(request,'env.html',{'env':env, 'envMod':envMod})
It works for Django 1.6 and uses the built-in login (look at the urls.py) and template. So you do not need to build a view function.
Info on urls