Django Model Authorisation - python

In my Django app, I've some CRUD operations over some models, which I want to be accessed only by the user who created that row. Here it's not role based authorisation as all users are same, I want them to be identified by the user who created it.
I tried something like:
class someModel(models.Model):
user = models.ForeignKey(User, db_index=True)
....
In my views, I would do the checking by:
def view(request, id):
model = somemodel.objects.get(id=id, user=request.user)
if model.user = request.user:
...
Would this be the easiest and yet be the correct way to do row level authorisation?

Ideally the views that need to check if the use is authenticated can have the login_required decorator. Then you can use try, except instead of if else because that's more pythonic
#login_required
def view(request, id):
try:
model = somemodel.objects.get(id=id, user=request.user) #this will raise an exception if not found
except Somemodel.DoesnotExist:
return HttpResponseRedirect('/login')
This of course begins to look boiler plate like. That's where Class Based Views come into the picture. Alternatively you can reduce the boiler plate code with
#login_required
def view(request, id):
my_object = get_object_or_404(SomeModel, pk=1, user=request.user)
# get_object_or_404 also throws a MultipleObjectsReturned exception
# when more then one object is returned, so catch it if needed.

You can even reduce one line of checking if model.user = request.user: as you already filtered the data with the logged in user in model = somemodel.objects.get(id=id, user=request.user)

class getUserData(APIView):
def get(self, request, format=None):
userName = request.data['userName']
try:
checkUserLoggedIn = YourAuthenticationModel.objects.get(id=id, user=userName)
getRow = someModel.objects.filter(user=userName)
except ObjectDoesNotExist:
print "User does not exist or logged In"

Related

Class Based Views Form neither Valid nor Invalid (Django)

I'm new to Django Class Based Views and I can't get my form to pass through neither form_valid() nor form_invalid().
I have taken most of this code from the Django allauth module, so I extend some mixins (AjaxCapableProcessFormViewMixin & LogoutFunctionalityMixin) that I do not know well.
This form is meant to allow users to change their passwords upon receiving an email. As it is now, users are able to change their password but since the form_valid() function is never triggered, they do no get redirected to the success URL as is intended. Instead the password change is registered but the users stay on the same page.
The functions dispatch(), get_form_kwargs() & get_form_class() are all triggered and behave in the way that they should. Still, it's unclear to me why they execute in the order that they do (dispatch() is triggered first, then get_form_class() and finally get_form_kwargs(). I suppose they implicitely have an order as presented in this documentation: https://ccbv.co.uk/projects/Django/4.0/django.views.generic.edit/FormView/)
I am lacking some intuition about how this works, therefore I don't know if there is a way to redirect to the success URL without passing through form_valid() because that would also solve my problem.
As is mentionned in the title, neither form_valid() nor form_invalid() is triggered after submitting a new password. The last executed bit of code is the return kwargs from the get_form_kwargs() function.
Here is my code:
class PasswordResetFromKeyView(AjaxCapableProcessFormViewMixin, LogoutFunctionalityMixin, FormView):
template_name = "account/password_reset_from_key." + app_settings.TEMPLATE_EXTENSION
form_class = ResetPasswordKeyForm
success_url = '/'
reset_url_key = "set-password"
def get_form_class(self):
return get_form_class(
app_settings.FORMS, "reset_password_from_key", self.form_class
)
def dispatch(self, request, uuid, **kwargs):
self.request = request
token = get_object_or_404(ResetToken, token=uuid)
if token.redeemed == False:
self.reset_user = token.client
self.token = token
response = self.render_to_response(self.get_context_data(token_fail=False))
else:
return super(PasswordResetFromKeyView, self).dispatch(
request, uuid, **kwargs
)
return response
def get_form_kwargs(self, **kwargs):
kwargs = super(PasswordResetFromKeyView, self).get_form_kwargs(**kwargs)
kwargs["user"] = self.reset_user
if len(kwargs) > 3:
try:
if kwargs['data']['password1'] == kwargs['data']['password2']:
self.reset_user.set_password(kwargs['data']['password1'])
self.reset_user.save()
self.token.redeemed = True
self.token.date_redeemed = datetime.now()
self.token.save()
perform_login(
self.request,
self.reset_user,
email_verification=app_settings.EMAIL_VERIFICATION,
)
else:
pass
##passwords dont match
except:
##couldnt change the password
pass
return kwargs
def form_valid(self, form, **kwargs):
form.save()
return super(PasswordResetFromKeyView, self).form_valid(form)
def form_invalid(self, form):
response = super().form_invalid(form)
if self.request.accepts('text/html'):
return response
else:
return JsonResponse(form.errors, status=400)
If both methods are not triggered, it means - you requests.method is never is 'POST'.
The class FormView calls this two methods only if post.method == 'POST':
# code from django.views.generic.edit
...
def post(self, request, *args, **kwargs):
"""
Handle POST requests: instantiate a form instance with the passed
POST variables and then check if it's valid.
"""
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
By the way in dispatch, if token.redeemed == False you should return self.form_invalid().

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)

Setting up conditional views in Django based on user permissions

I'm attempting to set up an index page in django that serves different content based on the user permissions (2+ types of users). I've looked into using the #permission_required decorator but it seems a bit wasteful and repetitive to use that with a view that's been mostly repeated. There's also no good fallback method that I can see (I can't do a #permission_required(!'jobs.can_edit')).
views.py:
#permission_required('jobs.can_add')
def index(request):
jobs = Job.objects.all
context = {
"jobs": jobs,
}
return render(request, 'jobs/index.html', context)
#permission_required('jobs.can_edit')
def index(request):
jobs = some.different.data
context = {
"jobs": jobs,
}
return render(request, 'jobs/index.html', context)
Is there an easier way to hook this into the index function and change the context based on user permissions? My ideal scenario would be more like this
imaginary views.py:
def index(request):
if user.can_add:
context = x
return render(request, 'jobs/index/can-add.html', context)
context = y
return render(request, 'jobs/index/can-edit.html', context)
I've also set the three user groups up by name, but I don't see much documentation on accessing group names.
If you are using django permission then you can do this in view
def index(request):
if request.user.has_perm('app_name.permission_name'):
#return something if true
#return something in else case
I recommend using groups and assigning permission to a group and check if a user belongs to a group in the view
try this.
I was inspired with that and I want to share my response too for others but in my case I use DRF and django group.
class ShopOrderCreateList(APIView):
permission_classes = [AllowAny]
#staticmethod
def get_object(shop_id):
try:
return Shop.objects.get(id=shop_id)
except Shop.DoesNotExist:
raise Http404
def get(self, request, shop_id=None):
shop = self.get_object(shop_id)
orders = Order.objects.filter(shop__id=shop.id)
# orders = manager.order_set.all()
# shop_orders = shop
# order_numbers = orders.count()
serializer = OrderSerializer(orders, many=True)
return Response(serializer.data)
#staticmethod
def post(request, shop_id=None):
if request.user.groups.filter(name='admin').exists():
manager = request.user.manager
elif request.user.groups.filter(name='master').exists():
master = request.user.master
manager = master.manager

How to test user ownership of object using Django decorators

I'm working on a Django project and trying to figure out how I can test for user ownership and allow editing or redirect based on the result.
I have a model Scene. Scene is linked to User to track which user created a particular Scene:
class Scene(models.Model):
user = models.ForeignKey(User)
[rest of Scene model]
I have a URL pattern to edit a particular Scene object like this:
url(r'^scenes/(?P<pk>[0-9]+)/edit/', SceneUpdateView.as_view(), name='scene-edit'),
I have a logged in user via django-allauth. I want only Scene owners to be able to edit Scenes.
I'm trying to figure out how to use a decorator to test if scene.user.id == self.request.user.id for the particular scene called by the URL.
Do I need to send URL information into permission_required or user_passes_test decorators (is this possible)?
How can I make this happen?
You can use a custom decorator for your specefic need.
Note: I'm using function based view, you will have to modify the code to class based view if you want:
import json
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_protect
from django.contrib.auth.models import User
from yourmodels.models import Scene
#Custom decorator
def must_be_yours(func):
def check_and_call(request, *args, **kwargs):
#user = request.user
#print user.id
pk = kwargs["pk"]
scene = Scene.objects.get(pk=pk)
if not (scene.user.id == request.user.id):
return HttpResponse("It is not yours ! You are not permitted !",
content_type="application/json", status=403)
return func(request, *args, **kwargs)
return check_and_call
#View Function
#must_be_yours
#csrf_protect
def update_scene(request, pk=None):
print pk
if request.method == 'PUT':
#modify merely
pass
Urls:
url(r'^scenes/(?P<pk>[0-9]+)/edit/', 'update_scene'),
In Function Based Views it's common to see decorators. Yet, in Class Based Views (CBV) it's more common to use Mixins or QuerySet.
Adapted from this answer, one can create the following custom Mixin that overrides the dispatch method
class UserOwnerMixin(object):
def dispatch(self, request, *args, **kwargs):
if self.object.user != self.request.user:
return HttpResponseForbidden()
return super(UserOwnerMixin, self).dispatch(request, *args, **kwargs)
This is a generalized way across multiple model class, as long as one is using user = models.ForeignKey(User).
Then use it in the CBV in a similar fashion to
class MyCustomView(UserOwnerMixin, View):

Categories

Resources