Django REST - Serialize User.get_all_permissions - django

I am building an application with a Django Rest backend, and a VueJS front end and am working through authorization and authentication. I have the authentication working well, but am a bit stuck on letting the front end (VueJS) know what the user has authorization to do in terms of Add/Change/View/Delete for a model. For example, if a user cannot add a customer, I don't want to show the 'Add Customer button'.
Working through the Django docs, and solutions on StackOverflow, I believe the simplest way is to send the user's permissions from Django to VueJS.
The 'best'/'simplest' way I can see to get the permissions is with the following:
userModel = User.objects.get(request.user)
return User.get_all_permissions(userModel)
Where I am stuck is exactly where to put this logic and how to serialize it. Does the above belong in the View, Serializer, other? Up until now, I have only been working with Models (ModelSerializers and ModelViews), but I don't believe this falls into this category.
Thanks in advance...

You should add this logic to views, because the views are used to implement these kinds of logic.
Actually, you don't want to use serializers here, because of the response of .get_all_permissions() method is already in serialized form
Apart from that, your provided code is not good (it's clearly bad). It should be as below,
return request.user.get_all_permissions()
because, you'll get current logged-in user's instance through request.user, to get his/her permissions, you all need to call the get_all_permissions() method
Example
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated
#permission_classes(IsAuthenticated, )
#api_view()
def my_view(request):
logged_in_user = request.user
return Response(data=logged_in_user.get_all_permissions())

Related

Django UserPassesTestMixin confusion/questions?

I am currently working on an admin dashboard, which include specific views for company administrators only which are labeled as Business users.
The app is going to have about 10 views, and I had a few questions in regards to the UserPassesTestMixin
Basically, all of my views would include this,
def test_func(self):
return self.request.user.user_type == 'Business'
To make sure the users are Business users I am protecting the views that way.
A couple questions that I am having trouble solving on my own are:
Now with that being repeated say 10 times, is there a cleaner way to do this, rather than having
def test_func in every CBV?
The other question that comes up, if the user doesn't pass test, it redirects to the login page, which I don't really like either. These views are all returning json. If the user does not pass test, I would like to just send them to something like,
JsonResponse({'message': 'Only company administrators have access to this view'})
How would I able to change that redirect only if the user does not pass test? Keeping in mind that these views also inherit from LoginRequiredMixin as well, in which if the user is not logged in I want to keep the original redirect to the login page in tact.
Any help with this is very appreciated. This side of Django is something fairly new to me!
Now with that being repeated say 10 times, is there a cleaner way to do this, rather than having def test_func in every CBV?
Yes, you can simply make a mixin that implements the check:
from django.contrib.auth.mixins import UserPassesTestMixin
class BusinessUserMixin(LoginRequiredMixin, UserPassesTestMixin):
def test_func(self):
return self.request.user.user_type == 'Business'
def handle_no_permission(self):
return JsonResponse(
{'message': 'Only company administrators have access to this view'}
)
and then you use this mixin in your views, for example:
class MyView1(BusinessUserMixin, ListView):
# …
pass
class MyView2(BusinessUserMixin, DetailView):
# …
pass
class MyView3(BusinessUserMixin, CreateView):
# …
pass
if the user doesn't pass test, it redirects to the login page, which I don't really like either. These views are all returning json. If the user does not pass test, I would like to just send them to something like.
You can override the handle_no_permission method as well, the view will return the result of this method as result when the test fails.

how can I securely perform Rest requests without authentication?

This is more a process logic question than a specific language-framework one.
I am developing a mobile app and want the user to be able to use it without having to login (i.e. try it and offer a plus to the logged users), but I don´t want other persons to make post requests from let´s say Postman or any other platform than the app without having some sort of key, so what would be the approach here?
I am thinking on basic auth with some secret username:password for guests, or some kind of token, but as I am totally new on this I am not sure if it´s the correct approach, I´ve read the authentication and permissions Django Rest Framework tutorial but haven´t found a solution
I am learning Django myself and have gotten to the more advanced topics in the subject. What you could do is create a function in your permissions.py file for this. like so:
from rest_framework import permissions
class specialMobileUserPermissions(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in request.SAFE_METHODS:
return True
if request.user.id == whatever your mobile users id is:
return false
return obj.id == request.user.id # if the user is a subscribed user and they are logged in return true
return false # because we need a way out if none of the above works
So when dealing with permissions classes the permissions.SAFE_PERMISSIONS is a list of permissions that are non-destructive. So the first if statement asks are you a GET, HEAD, or other non data altering method. If so return true.
The second if statement checks the user id of the user that is making the request. And if that user id is equal to the user id you set for the mobile trail user it would return false, denying permissions to whatever this class is used on.
In your viewset you would need to add the permissions_classes variable like below
from . import permissions # your permissions.py file
class FooViewSet(viewsets.ViewSet):
permission_classes = (permissions.specialMobileUserPermissions,)
Unless you need extra functionality, that should be everything you need, all the way down to the imports. I hope I have helped.

django-rest-framework authentication: require key parameter in URL?

I am working in Django 1.8 with the excellent django-rest-framework. I have a public RESTful API.
I would now like to start requiring a key GET parameter with this API, and disallowing any requests that do not have this parameter. I will allocate keys to users manually on request.
I have read through the DRF Authentication documentation, but I'm not sure there's anything that meets my use case. I find this strange, since my use case must be very common.
Token-based authentication requires the user to set an HTTP header. My typical API user is not sophisticated (Excel users who will be downloading CSVs), so I don't think I can ask them to do this.
I think Basic-Auth is what I need, but I'd much rather provide a simple URL-based key than a Django username and password (my app has no concept of users right now).
What is the best way to implement this?
Create a table which will contain all the keys that you issue to someone.
Example:
class RestApiKey(models.Model):
api_key = models.CharField(max_length=100)
Next create a custom Permision class which will check for the api Key in the url before forwarding the request to the view like:
from rest_framework import permissions
from yourappname.models import RestApiKey
class OnlyAPIPermission(permissions.BasePermission):
def has_permission(self, request, view):
try:
api_key = request.QUERY_PARAMS.get('apikey', False)
RestApiKey.objects.get(api_key=api_key)
return True
except:
return False
So the request url has to be like http://yourdomain.com/?apikey=sgvwregwrgwg
Next in your views add the permission class:
class YourViewSet(generics.ListAPIView):
permission_classes = (OnlyAPIPermission,)
or if you are using function based views then do like:
#permission_classes((OnlyAPIPermission, ))
def example_view(request, format=None):
. . .

Django Redirect after Successful Login

I have a requirement that whenever I login or attempt to request a view that has a login_decorator then the next page be a page where I am required to ask the user to select a business entity (irreespective of the original view requested).
Let's say that the page is http://127.0.0.1:8999/APP/business_unit
To achieve this I configured the following in my settings.py
LOGIN_REDIRECT_URL='/APP/business_unit_selector'
Now when i try to access http://127.0.0.1:8999/APP/range_list
the page goes to http://127.0.0.1:8999/APP/login?next=/APP/range_list I was expecting that the next page after login be /APP/business_unit
but instead, the next page was /APP/range_list
The browser address bar has http://127.0.0.1:8999/APP/login?next=/APP/range_list
Is it possible to achieve what I am trying in Django?
LOGIN_REDIRECT_URL is used unly when next is unspecified. In your test request there is next=/APP/range_list - and that address is used to redirect user after login.
Probably the easiest and most effective solution is to make your own decorator, similar to login_required which redirects to /APP/business_unit_selector&next=<redirect_url> if unit is not selected, and apply it together with login_required. It is not the most efficient solution in terms of redirects number, but is quite clean, and doesn't mess up the login page.
You will also have to handle next parameter in your business_unit_selector view, if you like to achieve natural flow.
Your decorator should be something like
from django.contrib.auth.decorators import login_required
from django.core.urlresolvers import reverse
from django.shortcuts import redirect
from django.utils.http import urlquote
import functools
def business_unit_required(view):
#login_required # probably you want to incorporate it here
#functools.wraps(view)
def wrapped(request, *args, **kwargs):
if not 'selected_business_unit' in request.session:
return redirect('%s?next=%s' % (
reverse('business_unit_selector'),
urlquote(request.get_full_path())))
return view(request, *args, **kwargs)
return wrapped
The reason that http://127.0.0.1:8999/APP/login?next=/APP/range_list is redirecting you to range_list after logging in, is because with next= you are overriding what is specified in your settings file, LOGIN_REDIRECT_URL='/APP/business_unit_selector'.
If I understand correctly you need to user to choose a business entity after logging in.
A couple solutions that come to mind are as follows:
1.) Don't use a separate forms for login and business entity. Instead combine them.
Username
Password
Business Entity
2.) You can also specify in your view if the user doesn't have a buisness entity ResponseRedirect("/APP/business_unit_selector")
docs here

add auth validation in Django

I have a django application and I want to add my own auth validation and check if the user is expired (check the expiration date from some of my models). I want to raise a ValidationError in the login page with appropriate message if user is expired. What's the best way to do that?
Thanks, Alex
If you REALLY want to do your own custom authentication, you should read custom backends in the Django Documentation.
You probably don't want to do your own though. It sucks. Really. Unless there is a really really good reason, you should avoid your own authentication. The main reason being, that many django apps stop working if you don't use the built in User model. If you need to authenticate against an existing source, that's a valid reason for creating your own backend. But there are pitfalls, and you still probably want to use the built in User model for your custom backend.
You should tell us why you want to do your own custom authentication, and perhaps we can help you achieve your requirement, without writing a custom backend.
Edit
Ok, I think I understand what you mean now. What (I think) you want, is a custom authentication form. We currently use custom form (though we have a different unavoidable backend), so you should be able to use the following quite easily.
from django.contrib.auth.forms import AuthenticationForm
from django import forms
from myproject.myapp.models import MyClass
class CustomAuthForm(AuthenticationForm):
def clean(self):
cleaned_data = super(CustomAuthForm, self).clean()
user = self.user_cache # set by super class
if user.my_class.expired:
raise forms.ValidationError('This User has Expired!')
return cleaned_data
Then, to use this custom authentication form, you need a URL in your urls.py:
from myproject.myapp.forms import CustomAuthForm
url(r'^login/$', 'django.contrib.auth.views.login', name='login',
kwargs={'template_name':'youproject/login.html', 'authentication_form':CustomAuthForm}),
I see now that your question originally stated you wanted custom validation, not authentication. My apology for not reading your question correctly.