How do I check that user already authenticated from tastypie? - ajax

When user authenticates in Django, how do I check that from tastypie?
Once user logs on, the view includes some JS that pulls data from API, which is backed by tastypie.
I have basic authentication/djangoauthorisation set up on my resources, so the browser pops up http auth window. Is there any way to avoid this?
My idea so far is to extend BasicAuthentication so that it first checks session data and when it doesn't find it, it falls back to http auth? AFAIK AJAX calls include session cookies, so this in theory should work? Has anybody done something similar?

I have this solution so far:
class MyBasicAuthentication(BasicAuthentication):
def __init__(self, *args, **kwargs):
super(MyBasicAuthentication, self).__init__(*args, **kwargs)
def is_authenticated(self, request, **kwargs):
from django.contrib.sessions.models import Session
if 'sessionid' in request.COOKIES:
s = Session.objects.get(pk=request.COOKIES['sessionid'])
if '_auth_user_id' in s.get_decoded():
u = User.objects.get(id=s.get_decoded()['_auth_user_id'])
request.user = u
return True
return super(MyBasicAuthentication, self).is_authenticated(request, **kwargs)
which seems to do what I want. If user is logged on, then session contains _auth_user_id, if not, the key is missing.
Anyone can think of any problems this approach may cause?

You may want to check out this ticket on tastypie's GitHub:
https://github.com/toastdriven/django-tastypie/issues/197
The author suggests a very clean approach to authenticate the call with both the session and the API key methods.
There goes the snippet:
class ApiKeyPlusWebAuthentication(ApiKeyAuthentication):
def is_authenticated(self, request, **kwargs):
if request.user.is_authenticated():
return True
return super(ApiKeyPlusWebAuthentication, self).is_authenticated(request, **kwargs)
def get_identifier(self, request):
if request.user.is_authenticated():
return request.user.username
else:
return super(ApiKeyPlusWebAuthentication, self).get_identifier(request)

Once the user is logged in through your API, you have a Django user session. If you want to check if the user is still logged in (on page refresh for example). You can do:
from tastypie.resources import Resource
class LoggedInResource(Resource):
class Meta:
pass
def get_list(self, request, **kwargs):
from django.http import HttpResponse
if request.user.is_authenticated():
return HttpResponse(status=200)
else:
return HttpResponse(status=401)
Client check:
$.ajax({
type: "GET",
url: '/api/loggedin/',
success: function(data) {
// logged in
},
error: function() {
// not logged in
}
});

Pulegium
Why not just as simple as the following:
class CommAuthentication(BasicAuthentication):
def __init__(self, *args, **kwargs):
super(CommAuthentication, self).__init__(*args, **kwargs)
def is_authenticated(self, request, **kwargs):
return request.user.is_authenticated()
I just start to learn tastypie. the above code seemed works for me. Any advantage of your solution ?

Related

login by one user and accesstoken of another user

I have implemented simpleJWT for token based authentication. I created a simple hello world test API.
While testing, I am logging with /rest-auth/login/ and for generating use /api/token/ - both working fine.
Now for testing, I am logging in with say user XYZ (having access rights for helloworld api) and generating token using another user ABC (not having access rights for helloworld api).
So now user XYZ is authenticated (ok) but I am having token of user ABC (ok).
Now, when I call the API with token generated for use ABC, I am able to access the helloworld api even if user ABC has no rights for the API !! Because user XYZ who has rights already logged in.
Problem is this will always be case when multiple users will be using the site. How to resolve ? Few code snippets also presented below :
My settings.py snipped
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
),
}
code basically a decorator which authenticate for users is as below
def user_is_ADM(fun_c):
#wraps(fun_c)
def wrapped(request, *args, **kwargs):
# 1 = ADM
if(request.user and request.user.is_authenticated) : <--- here is the issue
user_data = UserProfile.objects.get(user_id = request.user.id)
# user profile as as a user type
u = user_data.user_type
if u == 1:
return fun_c(request, *args, **kwargs)
else:
raise PermissionDenied
return wrapped
what should be my strategy in this case ?
EDIT
Modified my decorator as follows and it is working. Someone please comment if I am doing something wrong
def user_is_ADM(fun_c):
#wraps(fun_c)
def wrapped(request, *args, **kwargs):
juser = get_user(request)
if juser.is_authenticated:
user_jwt = JWTAuthentication().authenticate(Request(request))
if user_jwt is not None:
if request.user == user_jwt[0]:
k = user_jwt[0].userprofile.get_user_type_display()
if k == 'ADM':
return fun_c(request,*args,**kwargs)
else:
raise PermissionDenied
else:
raise PermissionDenied
else:
raise PermissionDenied
else:
raise PermissionDenied
return wrapped
check out this documentation https://www.django-rest-framework.org/api-guide/permissions/ (Custom permissions)
when setting a general permission setting (IsAuthenticated).
It is really authenticating users but not verifying their permissions at any time
class IsAuthenticated(BasePermission):
"""
Allows access only to authenticated users.
"""
def has_permission(self, request, view):
return bool(request.user and request.user.is_authenticated)
if basic administrator and user authentication is not enough. you can implement a custom permission
from rest_framework import permissions
class CustomerAccessPermission(permissions.BasePermission):
"""
extracted from the documentation
"""
message = 'Adding customers not allowed.'
def has_permission(self, request, view):
"""
add authentication logic and return a boolean value
"""
# ...
# return bool()
in the views
from rest_framework.views import APIView
from modulename.permissions import CustomerAccessPermission
class ExampleView(APIView):
"""
...
"""
permission_classes = (CustomerAccessPermission,)
def get(self, request, format=None):
"""
...
"""
Here is an example with django authentication permissions
from typing import Tuple
from rest_framework.permissions import BasePermission
class CustomPermission(BasePermission):
"""
...
"""
list_permissions: Tuple[str] = (
'modelname.view_modelname',
'modelname.add_modelname',
'modelname.change_modelname',
'modelname.delete_modelname',
)
def has_permission(self, request, view) -> bool:
return bool(
request.user.has_perms(self.list_permissions)
or
request.user and request.user.is_staff
or
request.user and request.user.is_superuser
)
summary
With regard to the syntax in general, you will not see any changes, you must import your permission or decorator in the view, the difference lies in the runtime and the way django will evaluate the permissions
Remembering that a decorator is nothing more than a summary way of saying func(func()).
so you should always evaluate the view and call your method modified by the decorator
instead, permissions in this framework are always defined as a list of permission classes. Before executing the main body of the view, each permission in the list is verified. If any permission verification fails, exceptions will be generated. Permission denied or exceptions. An unauthenticated exception will be generated and the main body of the view will not be executed. (Consult the documentation for a complete explanation)
You should evaluate which method is most appropriate for your case (performance, syntax, etc.).
# you can set your permission in the general settings to avoid importing into each file
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'modulename.permissions.CustomPermission',
),
# ...
}

Is there a way to use inheritance in django viewsets or do I have to rewrite the functions?

I am writting an API in Django Rest Framework. I am using viewsets however regular methods create() and update() do not do it for me and I have to rewrite them.
Suppose that I need to do just one check to test if creation of an instance is legit, do I still have to rewrite the whole create function?
Currently I have this:
class LocationViewSet(viewsets.ModelViewSet):
permission_classes = (IsAuthenticated,)
serializer_class = LocationSerializer
def create(self, request, *args, **kwargs):
user = request.user
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
if user.can_create_location():
serializer.save()
return Response(serializer.data)
else:
raise exceptions.PermissionDenied('You dont have permission to create this.')
Instead of rewritting the whole thing, is it possible to somehow use inheritance and do something like this?
class LocationViewSet(viewsets.ModelViewSet):
permission_classes = (IsAuthenticated,)
serializer_class = LocationSerializer
def create(self, request, *args, **kwargs):
user = request.user
if user.can_create_location():
return super().create(self, request, *args, **kwargs)
else:
raise exceptions.PermissionDenied('You dont have permission to create this.')
The above does not work as I get this error. I feel like this should be possible to do, but I don't know how. Thanks for your help!
AttributeError at /api/0/location
LocationViewSet' object has no attribute 'data'
Request Method: POST
Request URL: http://127.0.0.1:8000/api/0/location
Django Version: 2.1.7
Yes, it does work, and you're doing it almost correctly... as per your error: the function is trying to access LocationViewSet.data as you're passing self in the first argument, change that call to:
super().create(request, *args, **kwargs)
Python standard docs has some info on super() and a link to a more in depth explanation.

Overriding generic.ListView methods for AJAX requests DJANGO

I recently started using django's inbuilt generic views (Create, Update, etc) So I'm updating most of my old views to use them, one of them is the ListView, with pagination. So now, it works right,when i GET that page, it displays the objects as directed, and the pagination works fine. But i want to use AJAX on the pagination so that i just click a "More" button and it gets the next page's objects via ajax and are appended onto the end of the . So i've modified some generic views before to incorporate AJAX like the:
class Delete(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
login_url = LOGIN_URL
model = Items
success_url = reverse_lazy('web:member-area')
def test_func(self):
return not self.request.user.is_superuser and self.get_object().created_by == self.request.user
def delete(self, request, *args, **kwargs):
response = super().delete(request)
if self.request.is_ajax():
return JsonResponse({'success': 1}, status=200)
else:
return response
In the above snippet you can see i included the part where it returns something different if the request is AJAX
The current View that i'm working on is as follows:
class Items(ListView):
model = Items
paginate_by = 5
context_object_name = 'items'
template_name = 'web/items/index.html'
which works fine on normal GET requests, so the problem is i dont know which super() method(s) to override and return a different response if its AJAX on that ListView
Use dispatch
class Items(ListView):
def dispatch(request, *args, **kwargs):
response = super().dispatch(request, *args, **kwargs)
if request.is_ajax():
return JsonResponse({'success': 1}, status=200)
else:
return response

Django - Custom decorator to allow only ajax request

I have few views in my project which are called only by ajax and I need to put in a check that the views are processed only if the request is ajax. So I thought of writing a decorator. Upon searching for similar type of decorators I hit upon few but couldn't understand anything.
I make use of class based views and will use this decorator on get/post methods. I did find few snippets but all were written for function based views which made it more difficult for me to understand as I have never worked upon function based views.
I just need to know what is idea behind decorators and how it works.
from functools import wraps
from django.core.exceptions import PermissionDenied
def require_ajax(view):
#wraps(view)
def _wrapped_view(request, *args, **kwargs):
if request.is_ajax():
return view(request, *args, **kwargs)
else:
raise PermissionDenied()
return _wrapped_view
After a google search I've found this:
from django.http import HttpResponseBadRequest
def ajax_required(f):
"""
AJAX request required decorator
use it in your views:
#ajax_required
def my_view(request):
....
"""
def wrap(request, *args, **kwargs):
if not request.is_ajax():
return HttpResponseBadRequest()
return f(request, *args, **kwargs)
wrap.__doc__=f.__doc__
wrap.__name__=f.__name__
return wrap
Didn't tried it, so you have to try it. The essential part is request.is_ajax() which checks if the request is made through AJAX. Check also the docs for more info on is_ajax() method.
EDIT
To decorate a view class in django see Decorating the class in the documentation. Basically the decorator function wraps a method of the class. So you can use the django #method_decorator() to wrap a method in your decorator function (ajax_required):
#method_decorator(ajax_required)
def method_you_want_to_get_only_AJAX_requests():
......

login_required decorator on ajax views to return 401 instead of 302

While writing some views to respond to ajax requests i find it somewhat strange that the login_required decorator always returns a 302 status code for not authenticated users. As these views are ajax views, this seems somewhat inappropriate. I do not want the user to log in in such a case, but i want Django to tell the client that authentication is required to access such a view (a 401 should be the right status code, i think).
To achieve this, i started to write my own decorator login_required_ajax, but somehow this is beyond my skills. This is what i have come up with so far:
def login_required_ajax(function=None,redirect_field_name=None):
"""
Just make sure the user is authenticated to access a certain ajax view
Otherwise return a HttpResponse 401 - authentication required
instead of the 302 redirect of the original Django decorator
"""
def _decorator(view_func):
def _wrapped_view(request, *args, **kwargs):
if request.user.is_authenticated():
return view_func(request, *args, **kwargs)
else:
return HttpResponse(status=401)
if function is None:
return _decorator
else:
return _decorator(function)
When using this decorator on a view, i get a ViewDoesNotExist exception as soon as i try to access any page on the site.
I first thought that the problem could be the direct return of an HttpResponse when a user is not authenticated, because a response object is not a callable. But then the decorator should work as long as i do not try to access the view in question, shouldn't it? And if this really is the crux, how can i write a decorator that returns a HttpResponse with a status code of 401?
That's a pretty good attempt. Here's a couple of problems I spotted:
Your _decorator function should return _wrapped_view.
The indentation for your if function is None block is a bit off -- the login_required_ajax function needs to return the decorated function.
Here's the decorator with those changes made:
def login_required_ajax(function=None,redirect_field_name=None):
"""
Just make sure the user is authenticated to access a certain ajax view
Otherwise return a HttpResponse 401 - authentication required
instead of the 302 redirect of the original Django decorator
"""
def _decorator(view_func):
def _wrapped_view(request, *args, **kwargs):
if request.user.is_authenticated():
return view_func(request, *args, **kwargs)
else:
return HttpResponse(status=401)
return _wrapped_view
if function is None:
return _decorator
else:
return _decorator(function)

Resources