How to use django-tastypie with django-axes - python

Settings:
django==1.8
django-tastypie==0.13
django-axes==2.3
I've got login resource through tastypie what looks like below
from django.contrib.auth import login
class LoginResource(Resource):
class Meta:
resource_name = 'login'
allowed_methods = ['post']
def obj_create(self, bundle, **kwargs):
form = AuthForm(data=bundle.data)
if form.is_valid():
request.session.set_expiry(0)
if form.get_user():
login(bundle.request, form.get_user())
raise ImmediateHttpResponse(response=HttpResponse(status=200))
raise ImmediateHttpResponse(response=http.HttpBadRequest(status=400))
And I can't figure out how to log these login attempts in django-axes.

My own solution was next: I write custom login view in views.py
from django.contrib.auth.views import login
def core_login(request, *args, **kwargs):
kwargs["authentication_form"] = AuthForm
return login(request, *args, **kwargs)
And in tastypie resource:
from core.views import core_login
class LoginResource(Resource):
class Meta:
resource_name = 'login'
allowed_methods = ['post']
def obj_create(self, bundle, **kwargs):
bundle.request.POST = bundle.data
if core_login(bundle.request).status_code == 302:
raise ImmediateHttpResponse(response=HttpResponse(status=200))
raise ImmediateHttpResponse(response=http.HttpBadRequest(status=400))

Looking at the code from django-axes we can see it uses a decorator called watch_login to provide its functionality.
To log the login attempts using your resource you will have to apply that decorator to the view that is called when the user tries to login using that given resource.
Based on tastypie code, you could override prepend_urls method of your resource and add your url. Like this (this is just an example, wasn't tested):
def prepend_urls(self):
from axes.decorators import watch_login
urls = [
url(r"^(?P<resource_name>%s)/login$" % (self._meta.resource_name,), watch_login(self.wrap_view('dispatch_list')), name="login-enpoint")
]
return urls

Related

How to restrict access to certain groups in django class based view

My views.py have a mix of def and ClassViews:
#login_required(login_url='login')
#allowed_users(allowed_roles=['Admin', 'Staff', 'Lite Scan'])
def litescan(request):
filteredOutput = Stock.objects.all()
val = {}...
#method_decorator(login_required(login_url='login'), name='dispatch')
class HomeView(ListView):
model = Post
template_name = 'community.html'
ordering = ['-id']
And here's my decorators.py if that is helpful:
from django.shortcuts import redirect
from django.http import HttpResponseRedirect
def unauthenticated_user(view_func):
def wrapper_func(request, *args, **kwargs):
if request.user.is_authenticated:
return redirect('home')
else:
return view_func(request, *args, **kwargs)
return wrapper_func
def allowed_users(allowed_roles=[]):
def decorator(view_func):
def wrapper_func(request, *args, **kwargs):
group = None
if request.user.groups.exists():
group = request.user.groups.all()[0].name
if group in allowed_roles:
return view_func(request, *args, **kwargs)
else:
url = ('/forbidden')
return HttpResponseRedirect(url)
return wrapper_func
return decorator
I found out that #login_required and #allowed_users give out an error when used with ClassView. So i used #method_decorator which brings me to the login page before redirecting to the page. However, I can not find a way to restrict access to only certain groups like Admin, Staff, Lite Scan with my ClassView.
Little help will be appreciated. Thanks!
You can use AccessMixin for your class views.
Example I found:
from django.contrib.auth.mixins import AccessMixin
from django.http import HttpResponseRedirect
class FinanceOverview(AccessMixin, TemplateMixin):
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
# This will redirect to the login view
return self.handle_no_permission()
if not self.request.user.groups.filter(name="FinanceGrp").exists():
# Redirect the user to somewhere else - add your URL here
return HttpResponseRedirect(...)
# Checks pass, let http method handlers process the request
return super().dispatch(request, *args, **kwargs)
More info found here: Use LoginRequiredMixin and UserPassesTestMixin at the same time
Relying on Django Permissions may be a far simpler approach to giving access to such a view. Rather than checking for a specific list of groups, you can assign permissions to those groups and give access to the view based on whether the user's groups have the appropriate permissions.
views.py
from django.contrib.auth.decorators import permission_required
from django.contrib.auth.mixins import PermissionsRequiredMixin
#permission_required('foo.view_bar')
def my_view(request):
...
class MyView(PermissionRequiredMixin, DetailView):
permission_required = ('foo.view_bar', )
...

How do I create links in Django such that other users can't access them?

I'm pretty confused about how do I prevent users' from accessing the data of other users.
The case at hand :
I'm creating a Notes + To-Do app in which a user logs in, creates their notes and tasks.
How to create links to those notes such that they aren't accessible by other users? As in the correct syntax for UserPassesTestMixin.
In the To-Do app, how do I keep the tasks of one user unique to them? Similarly for the note app, how do I achieve that?
Not sure what you mean by "create links". For what you describe, the links don't change for people that have access or not. The difference if that a user that owns note 5 and goes to /note/5/, they should be able to see their note, but if another user goes to /note/5/ they should either 1) get a 404 error (Note not found) or 403 (Permission Denied) just be redirected to another page (say, the home page), maybe with a message.
Using Class based views, this is easy to do.
Prevent access to views
from django.core.exceptions import PermissionDenied
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
class LoginRequiredAccessMixin(object):
# This will ensure the user is authenticated and should
# likely be used for other views
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
return super(LoginRequiredAccessMixin, self).dispatch(request, *args, **kwargs)
class AccessMixin(LoginRequiredAccessMixin):
def get_object(self, queryset=None):
obj = get_object_or_404(Note, pk=self.kwargs['id'])
# Assumes you have a notes.user, but change to created_by
# or whatever is your user field name
if obj.user == self.request.user:
# User owns object
return obj
raise PermissionDenied("User has no access to this note")
class NoteView(AccessMixin, DetailView):
# This is a regular DetilView, but with the Mixin,
# you are overwriting the get_object() function.
# If you don't want the Mixin, then you can just add
# get get_object() function here. Except that with the
# Mixin, you can reuse it for your UpdateView, DeleteView
# and even across both your notes and task views
model = Note
template_name = 'note/details.html'
def get_context_data(self, **kwargs):
context = super(NoteView, self).get_context_data(**kwargs)
# Add any special context for the template
return context
If instead you want to just direct users to another page, you would do something like:
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import login_required
from django.contrib import messages
class NoteView(DetailView):
model = Note
template_name = 'note/details.html'
def get_context_data(self, **kwargs):
context = super(NoteView, self).get_context_data(**kwargs)
# Add any special context for the template
return context
#method_decorator(login_required)
def dispatch(self, request, *args, **kwargs):
note = self.get_objet()
if note and not note.user == self.request.user:
messages.error(
self.request,
'You are not allowed to access this Note'
)
return HttpResponseRedirect('/home')
return super(NoteView, self).dispatch(request, *args, **kwargs)
You didn't supply any code so I cannot be more specific, but hopefully you get an idea of the two techniques. The first is usually a cleaner solution, and the Mixin I show can be shared across both your Note views and ToDo Tasks records, assuming they use the same user/created_by field name.
In case you are using functions (FBV) you could use if request.user == item.user
#login_required
def post_edit(request, post_id):
item = Post.objects.get(pk=post_id)
if request.user == item.user:
CBV - Class Based View - using UserPassesTestMixin
class PostUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
# [...]
You can use the decorator in Django called "user_passes_test"
You can import like:
from django.contrib.auth.decorators import user_passes_test
For detail check docs here

How can I authenticate a user with a query parameter on any url?

Let's say the user lands on https://example.com/any/page?token=hhdo28h3do782.
What's the recommended way to authenticate and login a user with the query string?
I was thinking about creating some sort of catch-all view (I'd also like to know how to do this :D) that calls authenticate(). Then I would have in place a custom backend that would authenticate the user.
Is this the ideal way to achieve what I want?
Cheers!
To do this, you need to create a custom authentication backend that validates api keys.
In this example, the request is checked for a valid token automatically. You don't need to modify and of your views at all. This is because it includes custom middleware that authenticates the user.
For brevity, I'm assuming that the valid user tokens are stored in a model that is foreign keyed to the django auth.User model.
# my_project/authentication_backends.py
from django.contrib import auth
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User
from django.contrib.auth.middleware import AuthenticationMiddleware
TOKEN_QUERY_PARAM = "token"
class TokenMiddleware(AuthenticationMiddleware):
def process_request(self, request):
try:
token = request.GET[TOKEN_QUERY_PARAM]
except KeyError:
# A token isn't included in the query params
return
if request.user.is_authenticated:
# Here you can check that the authenticated user has the same `token` value
# as the one in the request. Otherwise, logout the already authenticated
# user.
if request.user.token.key == token:
return
else:
auth.logout(request)
user = auth.authenticate(request, token=token)
if user:
# The token is valid. Save the user to the request and session.
request.user = user
auth.login(request, user)
class TokenBackend(ModelBackend):
def authenticate(self, request, token=None):
if not token:
return None
try:
return User.objects.get(token__key=token)
except User.DoesNotExist:
# A user with that token does not exist
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
Now, you can add the paths to AUTHENTICATION_BACKENDS and MIDDLEWARE in your settings.py in addition to any existing backends or middleware you may already have. If you're using the defaults, it would look like this:
MIDDLEWARE = [
# ...
"django.contrib.auth.middleware.AuthenticationMiddleware",
# This is the dotted path to your backend class. For this example,
# I'm pretending that the class is in the file:
# my_project/authentication_backends.py
"my_project.authentication_backends.TokenMiddleware",
# ...
]
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"my_project.authentication_backends.TokenBackend",
]
I assume you are using the Django REST Framework and also enabled the TokenAuthentication mechanism in your project. If so, go ahead with this,
from rest_framework.authentication import TokenAuthentication
class QueryParamAuthentication(TokenAuthentication):
query_param_name = 'token'
def authenticate(self, request):
token = request.query_params.get(self.query_param_name)
if token:
return self.authenticate_credentials(token)
return None
and then, change DRF DEFAULT_AUTHENTICATION_CLASSES as
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'dotted.path.to.QueryParamAuthentication'
),
# rest of your DRF settings...
}
Update
to do this without DRF, you have to write custom model backend (which is a bit lengthy topic)
Refer: Writing an authentication backend
So, start with a way of managing your tokens. Here's a basic model:
class Token(models.Model):
code = models.CharField(max_length=255)
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
expires = models.DateTimeField()
A custom authentication backend can be produced to check the validity of the tokens:
class TokenAuthenticationBackend(ModelBackend):
def authenticate(self, request, token=None):
try:
token = Token.objects.get(code=token, expires__gte=now())
except Token.DoesNotExist:
return None
else:
return token.user
If you're using class-based views, you could write a mixin that checks for the presence of the token then does your authentication logic:
class UrlTokenAuthenticationMixin:
def dispatch(self, request, *args, **kwargs):
if 'token' in request.GET:
user = authenticate(request, request.GET['token'])
if user:
login(request, user)
return super(UrlTokenAuthenticationMixin, self).dispatch(request, *args, **kwargs)
To use this on a given view, just declare your views as follows:
class MyView(UrlTokenAuthenticationMixin, TemplateView):
# view code here
For example.
An alternative way to implement this as a blanket catch-all would be to use middleware rather than a mixin:
class TokenAuthMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if 'token' in request.GET:
user = authenticate(request, request.GET['token'])
if user:
login(request, user)
return self.get_response(request)

Check permissions on a related object in Django REST Framework

I have defined the following models
class Flight(models.Model):
...
class FlightUpdate(models.Model):
flight = models.ForeignKey('Flight', related_name='updates')
...
and the following viewset using the NestedViewsetMixin in the REST Framework Extensions
class FlightUpdateViewSet(mixins.ListModelMixin,
mixins.CreateModelMixin,
NestedViewSetMixin,
viewsets.GenericViewSet):
"""
API Endpoint for Flight Updates
"""
queryset = FlightUpdate.objects.all()
serializer_class = FlightUpdateSerializer
def create(self, request, *args, **kwargs):
flight = Flight.objects.get(pk=self.get_parents_query_dict()['flight'])
...
So, to access the FlightUpdates associated with a Flight, the URL is /flights/1/updates/.
I want to ensure that people can only create FlightUpdates if they have the permissions to change the Flight object with which the FlightUpdate is associated.
How would I go about performing the extra check when adding a FlightUpdate? I've tried adding something like this in the viewset, but I'm not sure if it's the best way.
if not request.user.has_perm('flights.change_flight', flight):
raise PermissionError()
Note: I'm using django-rules for the object-level permissions implementation.
I solved this problem by implementing a custom permissions class.
from django.core.exceptions import ObjectDoesNotExist
from rest_framework.permissions import BasePermission, SAFE_METHODS
from .models import Flight
class FlightPermission(BasePermission):
def has_permission(self, request, view):
if request.method in SAFE_METHODS:
return True
try:
flight = Flight.objects.get(pk=view.kwargs['parent_lookup_flight'])
except ObjectDoesNotExist:
return False
return request.user.has_perm('flights.change_flight', flight)

Override the authToken views in Django Rest

I am using the token based Authentication in Django and need to add User object in addition to token being returned.
How do I override this class view ? Where do I need add this class and make the changes ? Currently this is found in the rest_framework package and I don't want to modify the library .
from rest_framework import parsers, renderers
from rest_framework.authtoken.models import Token
from rest_framework.authtoken.serializers import AuthTokenSerializer
from rest_framework.response import Response
from rest_framework.views import APIView
class ObtainAuthToken(APIView):
throttle_classes = ()
permission_classes = ()
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
renderer_classes = (renderers.JSONRenderer,)
serializer_class = AuthTokenSerializer
print "dasdsa"
def post(self, request):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
return Response({'token': token.key})
obtain_auth_token = ObtainAuthToken.as_view()
From docs.
First that you need is to extend the ObtainAuthToken class.
# views.py
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
class CustomAuthToken(ObtainAuthToken):
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data,
context={'request': request})
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
return Response({
'token': token.key,
'user_id': user.pk,
'email': user.email
})
And after this add the CustomAuthToken class to your urls.py like view
# urls.py
from django.urls import path
from . import views
urlpatterns += [
path(r'api-token-auth/', views.CustomAuthToken.as_view())
]
You should extend your CustomClass from AuthToken, the route default url to your CustomClass:
from rest_framework_jwt.views import ObtainJSONWebToken
class JSONWebTokenAPIOverride(ObtainJSONWebToken):
"""
Override JWT
"""
def post(self, request):
# Do whatever you want
Then in your urls.py:
url(
r'^api-auth$',
cache_page(0)(views.JSONWebTokenAPIOverride.as_view())
)
I hope it helps
I wanted to override some default CRSF functionality and used the following approach:
from rest_framework.authentication import SessionAuthentication
class SessionCsrfExemptAuthentication(SessionAuthentication):
def enforce_csrf(self, request):
# Do not perform a csrf check
return False
Then in my settings file I referenced it in the following way:
'DEFAULT_AUTHENTICATION_CLASSES': (
'myapp.utils.authenticate.SessionCsrfExemptAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'oauth2_provider.ext.rest_framework.OAuth2Authentication',
'rest_framework_social_oauth2.authentication.SocialAuthentication',
),
This allowed me to import the existing functionality, override it and reference it in the settings file. I think you can use a similar approach here.
I use the option JWT_RESPONSE_PAYLOAD_HANDLER.
In the response I include the token, expiration timestamp and the user.
In settings.py add:
JWT_AUTH = {
...
'JWT_RESPONSE_PAYLOAD_HANDLER':'<app_name>.functions.custom_jwt_response',
}
Then in functions.py add the following
def custom_jwt_response(token, user=None, request=None):
import jwt
jwt = jwt.decode(token, verify=False)
return {
'token': token,
'token_exp': jwt['exp'],
'user': UserSerializer(user, context={'request': request}).data
}
The answers here are good but in my opinion they don't make full use of inheritance. When we inherit a class, we shouldn't just try to reinvent the wheel and instead make use of the super() keyword. Here is my code example, where I want to turn the username argument into lowercase before performing the authentication request:
class GetAuthToken(ObtainAuthToken):
"""
Override Django's ObtainAuthToken to provide custom way of authenticating user for token
"""
def post(self, request, *args, **kwargs):
#-- turn username to lowercase
if ('username' in request.data):
request.data['username'] = request.data['username'].lower()
#-- perform normal function
return super().post(request, *args, **kwargs)

Categories

Resources