I have such api method:
#api_view(['POST'])
#login_required
def get_posts(request):
# ...
How can I disable CSRF only on this method?
For function based views you can usually use the decorator csrf_exempt:
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
#csrf_exempt
def my_view(request):
return HttpResponse('Hello world')
Update: There may be an exception for the DRF. Take a look here.
Related
I have two views.
class IndexView(TemplateView):
template_name = 'index.html'
#require_POST
#login_required
def partial_view(request):
return render(request, 'partials/stuff.html')
I want the index page to be "public" but if user takes action (which triggers partial view), they should be redirected to LOGIN_URL, if not logged in.
The problem is that my partial view will return the entire LOGIN_URL page. So there's a page within a page now.
Is it possible to redirect the "parent" page when using partial views?
I didn't manage to make "post-login redirection" work but my solution is good enough for my needs.
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.conf import settings
from django.contrib.auth.decorators import login_required as django_login_required
from django.http import HttpResponse
from functools import wraps
from django.shortcuts import resolve_url
def login_required(function=None, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
#wraps(function)
def wrapper(request, *args, **kwargs):
if not request.user.is_authenticated and request.htmx:
resolved_login_url = resolve_url(login_url or settings.LOGIN_URL)
return HttpResponse(status=204, headers={'HX-Redirect': resolved_login_url})
return django_login_required(
function=function,
login_url=login_url,
redirect_field_name=redirect_field_name
)(request, *args, **kwargs)
return wrapper
I have the following Decorator which works fine when applied to different views with: #otp_required(login_url='login') on my site:
Decorator
from django.contrib.auth.decorators import user_passes_test
from django_otp import user_has_device
from django_otp.conf import settings
def otp_required(view=None, redirect_field_name='next', login_url=None, if_configured=False):
"""
Similar to :func:`~django.contrib.auth.decorators.login_required`, but
requires the user to be :term:`verified`. By default, this redirects users
to :setting:`OTP_LOGIN_URL`.
:param if_configured: If ``True``, an authenticated user with no confirmed
OTP devices will be allowed. Default is ``False``.
:type if_configured: bool
"""
if login_url is None:
login_url = settings.OTP_LOGIN_URL
def test(user):
return user.is_verified() or (if_configured and user.is_authenticated and not user_has_device(user))
decorator = user_passes_test(test, login_url=login_url, redirect_field_name=redirect_field_name)
return decorator if (view is None) else decorator(view)
However, I’d like to convert this into a Middleware as I want to avoid having to apply a decorator to every view on my site, but not managed to get working.
I tried to amend the following Middleware which I currently have in place which is just for authorised users and has been working but as per above decorator I want this Middleware extended to also have OTP required as well:
Middleware
from django.utils.deprecation import MiddlewareMixin
from django.urls import resolve, reverse
from django.http import HttpResponseRedirect
from wfi_workflow import settings
from django_otp import user_has_device
from django_otp.decorators import otp_required
from django_otp.middleware import is_verified
class LoginRequiredMiddleware(MiddlewareMixin):
"""
Middleware that requires a user to be authenticated to view any page other
than LOGIN_URL. Exemptions to this requirement can optionally be specified
in settings by setting a tuple of routes to ignore
"""
##otp_required(login_url='login')
def process_request(self, request):
assert hasattr(request, 'user'), """
The Login Required middleware needs to be after AuthenticationMiddleware.
Also make sure to include the template context_processor:
'django.contrib.account.context_processors.account'."""
if not request.user.is_verified() and not request.path.startswith('/admin/') and not request.path.startswith('/account/' ):
current_route_name = resolve(request.path_info).url_name
if not current_route_name in settings.AUTH_EXEMPT_ROUTES:
return HttpResponseRedirect(reverse(settings.LOGIN_URL))
Help is much appreciated.
The fact that you return a HttpResponseRedirect will not work: Django's MiddlewareMixin will simply call the function to (optionally) alter the request, but it will never take the return into account.
What you can do is define middleware in a decorator-like structure, and return the HttpResponseRedirect in case the user should be authenticated with:
from django.urls import resolve, reverse
from django.http import HttpResponseRedirect
from wfi_workflow import settings
def OTPRequiredMiddleware(get_response):
"""
Middleware that requires a user to be authenticated to view any page other
than LOGIN_URL. Exemptions to this requirement can optionally be specified
in settings by setting a tuple of routes to ignore
"""
def middleware(request):
from django_otp import user_has_device
if not user.is_verified() and not (if_configured and user.is_authenticated and not user_has_device(user)):
return HttpResponseRedirect(settings.OTP_LOGIN_URL)
return get_response(request)
I was wondering what's up with the CSRF Cookie not set error that Django throws at me all the time. I created a view (see below) that is a callback for a payment. I have no influence of what is being sent to that view. I have checked other posts on StackOverflow, but I don't think any apply to me. Most of them can just implement a csrf protection into their forms, csrf_exempt their views, or they use rest_framework.
class PaymentWebhook(View):
def post(self, request, *args, **kwargs):
# ...
Now, I'm getting this error everytime when I do nothing about this function:
Forbidden (CSRF cookie not set.): /payment/webhook
Since this is about payments, can I just csrf_exempt this, or would that just open a security hole? By the way, I have even tried putting an exempt on this function, but it still throws that error.
Any suggestions?
I finally managed to get a POST through. I'm fairly sure I had already tested it, but it seemed to work now: I put an csrf_exempt in my urls.py.
from django.views.decorators.csrf import csrf_exempt
url(r'^payment/webhook$', csrf_exempt(paymentwebhook), name='payment-webhook')
Any other way it would not work for me for some reason. Thanks nik_m for the help, appreciate it!
You should decorate the dispatch method with the csrf_exempt, like this:
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
class PaymentWebhook(View):
#method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
return super(PaymentWebhook, self).dispatch(request, *args, **kwargs) # for python 2
return super().dispatch(request, *args, **kwargs) # for python 3
def post(self, request, *args, **kwargs):
# ...
or, based on this, you can clean it to:
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
#method_decorator(csrf_exempt, name='dispatch')
class PaymentWebhook(View):
def post(self, request, *args, **kwargs):
# ...
Here is what i did ( import csrf_exempt )
from django.views.decorators.csrf import csrf_exempt
#csrf_exempt
#api_view(['POST'])
def add(request):
Simple. Import csrf_exempt:
from django.views.decorators.csrf import csrf_exempt
and then add the decorator:
#csrf_exempt
def your_function(request):
...
I use this for my payment webhooks and it has not been an issue. If you are particularly worried about security you can get the IP address from the request and only process the webhook if it matches your payment providers IP. Usually obtained from their website/api docs.
When I try to use staff_view, I get redirected in the admin authentication interface.
from django.contrib.admin.views.decorators import staff_member_required
#staff_member_required
def staff_view(request..):
...
How can I make a custom login, and not getting redirected in the default admin login interface?
You can use Django's user_passes_test decorator:
from django.contrib.auth.decorators import user_passes_test
#user_passes_test(lambda u:u.is_staff, login_url=reverse_lazy('foo'))
def staff_view(request..):
...
It's worth pointing out that staff_member_required is a wrapper around user_passes_test (see source code) and it also accepts a login_url parameter, so you could do:
#staff_member_required(login_url=example_url)
def staff_view(request..):
...
I'm trying to write a site in Django where the API URLs are the same as user-facing URLs. But I'm having trouble with pages which use POST requests and CSRF protection. For example, if I have a page /foo/add I want to be able to send POST requests to it in two ways:
As an end user (authenticated using a session cookie) submitting a form. This requires CSRF protection.
As an API client (authenticated using a HTTP request header). This will fail if CSRF protection is enabled.
I have found various ways of disabling CSRF, such as #csrf_exempt, but these all disable it for the entire view. Is there any way of enabling/disabling it at a more fine-grained level? Or am I just going to have to implement by own CSRF protection from scratch?
Modify urls.py
If you manage your routes in urls.py, you can wrap your desired routes with csrf_exempt() to exclude them from the CSRF verification middleware.
for instance,
from django.views.decorators.csrf import csrf_exempt
urlpatterns = patterns(
# ...
# Will exclude `/api/v1/test` from CSRF
url(r'^api/v1/test', csrf_exempt(TestApiHandler.as_view()))
# ...
)
Alternatively, as a Decorator
Some may find the use of the #csrf_exempt decorator more suitable for their needs
for instance,
from django.views.decorators.csrf import csrf_exempt
from django.http import HttpResponse
#csrf_exempt
def my_view(request):
return HttpResponse('Hello world')
There is a section of Django's CSRF Protection documentation titled View needs protection for one path which describes a solution. The idea is to use #csrf_exempt on the whole view, but when the API client header is not present or invalid, then call a function
annotated with #csrf_protect.
If you are you using class base view (CBV) and want to use the csrf_exempt decorator you will need to use the method decorator.
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt
#method_decorator(csrf_exempt, name='dispatch')
class MyView(View):
def post(self, request):
pass # my view code here
In my case, I am using JWT authentication plus csrf_token for some views. And for some reasons that I am unaware of, csrf_exempt does not work when I set it as a decorator or when I wrap the view name in the url patterns.
So here's what I ended up doing. I overrided the initialize_request available in the APIView class.
class ClasssName(views.APIView):
def initialize_request(self, request, *args, **kwargs):
setattr(request, 'csrf_processing_done', True)
return super().initialize_request(request, *args, **kwargs)