Permission denied raised from decorator django - python

I have an Image model that has a user field foreignkey from django user
class Image(models.Model):
image = models.ImageField()
user = models.ForeignKey(User)
#more specific model fields
I want media files to be served only to users that are logged in and uploaded the media. So I used X-SendFile header in apache using the following view
#owns_media
def media_xsendfile(request, path):
print "inside media_xsendfile view"
print os.path.join(settings.MEDIA_ROOT,path)
response = HttpResponse()
response['Content-Type']=''
response['X-Sendfile']= smart_str(os.path.join(settings.MEDIA_ROOT, path))
return response
The owns_media is a decorator that checks if the user logged in is the user that has uploaded the picture and permitts the view to run or raise a PermissionDenied exception. This is the decorator
def owns_media(view):
"""Decorator to check if users has permission to access media"""
def wrapper(request, *args, **kw):
path = kw['path']
user = request.user
image = Image.objects.get(image=path)
image_user = image.user
if user==image_user:
return view(request, *args, **kw)
else:
raise PermissionDenied
return wrapper
But it won't work. I loggedn in to a user got the picture, then logged out and tried to access it and it did serve the picture while it shouldn't. Am I doing something wrong?

Related

DRF and Token authentication with safe-deleted users?

I'm using a Django package named django-safedelete that allows to delete users without removing them from the database.
Basically, it adds a delete attribute to the model, and the queries like User.objects.all() won't return the deleted models.
You can still query all objects using a special manager. For example User.objects.all_with_deleted() will return all users , including the deleted ones. User.objects.deleted_only() will return the deleted ones.
This works as expected, except in one case.
I'm using Token Authentication for my users with Django Rest Framework 3.9, and in my DRF views, I'm using the built-in permission IsAuthenticated.
Code of a basic CBV I'm using:
class MyView(APIView):
permission_classes = (IsAuthenticated,)
def get(self, request):
return Response(status=HTTP_200_OK)
Code of the DRF implementation of IsAuthenticated permission:
class IsAuthenticated(BasePermission):
"""
Allows access only to authenticated users.
"""
def has_permission(self, request, view):
return bool(request.user and request.user.is_authenticated)
The problem
When a user is soft deleted, he's still able to authenticate using its token.
I'm expecting the user to have a 401 Unauthorized error when he's soft deleted.
What's wrong?
The DRF already uses the is_active property to decide if the user is able to authenticate. Whenever you delete a user, just be sure to set is_active to False at the same time.
For django-safedelete:
Since you're using django-safedelete, you'll have to override the delete() method to de-activate and then use super() to do the original behavior, something like:
class MyUserModel(SafeDeleteModel):
_safedelete_policy = SOFT_DELETE
my_field = models.TextField()
def delete(self, *args, **kwargs):
self.is_active = False
super().delete(*args, **kwargs)
def undelete(self, *args, **kwargs):
self.is_active = True
super().undelete(*args, **kwargs)
Note that this works with QuerySets too because the manager for SafeDeleteModel overrides the QuerySet delete() method. (See: https://github.com/makinacorpus/django-safedelete/blob/master/safedelete/queryset.py)
The benefit to this solution is that you do not have to change the auth class on every APIView, and any apps that rely on the is_active property of the User model will behave sanely. Plus, if you don't do this then you'll have deleted objects that are also active, so that doesn't make much sense.
Why?
If we look into the authenticate_credentials() method of DRF TokenAuthentication [source-code], we could see that,
def authenticate_credentials(self, key):
model = self.get_model()
try:
token = model.objects.select_related('user').get(key=key)
except model.DoesNotExist:
raise exceptions.AuthenticationFailed(_('Invalid token.'))
if not token.user.is_active:
raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))
return (token.user, token)
Which indicates that it's not filtering out the soft deleted User instances
Solution?
Create a Custom Authentication class and wire-up in the corresponding view
# authentication.py
from rest_framework.authentication import TokenAuthentication, exceptions, _
class CustomTokenAuthentication(TokenAuthentication):
def authenticate_credentials(self, key):
model = self.get_model()
try:
token = model.objects.select_related('user').get(key=key)
except model.DoesNotExist:
raise exceptions.AuthenticationFailed(_('Invalid token.'))
if not token.user.is_active or not token.user.deleted: # Here I added something new !!
raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))
return (token.user, token)
and wire-up in views
# views.py
from rest_framework.views import APIView
class MyView(APIView):
authentication_classes = (CustomTokenAuthentication,)
permission_classes = (IsAuthenticated,)
def get(self, request):
return Response(status=HTTP_200_OK)

Creating a Django demo-user who can't save to the database

I'm creating a Django web application on which users can create an account for free. I have also set-up a demo user which is already configured and has data attached to its account. Purpose of this demo account is to give a new user a quick overview of what the application can do.
Now I would like to have this demo user access all my views but not save to the database when the user saves a form.
Off course there are multiple ways off doing this that I know off. But they all require me to edit multiple pages or views:
When saving a form check if it is the demo user, if yes: don't save
Remove the save button from my templates when the demo user is logged in
Is there a easier/cleaner solution to do this? How can I set-up my application in a way that a specific user can never save to the database?
The solution I used
marcusshep's idea provided a solution for me. I created the following Views for pages where the form should be loaded but not saved when hitting the save button. Until now I wasn't able to do that. At this moment the pages below will render a 303 immediately
class FormViewOPRadio(FormView):
def dispatch(self, request, *args, **kwargs):
# Return 403 for demo user
temp = 'temp'
if self.request.user.email == 'demo#opradio.nl':
raise PermissionDenied
else:
return super(FormViewOPRadio, self).dispatch(request, *args, **kwargs)
class UpdateViewOPRadio(UpdateView):
def dispatch(self, request, *args, **kwargs):
# Return 403 for demo user
temp = 'temp'
if self.request.user.email == 'demo#opradio.nl':
raise PermissionDenied
else:
return super(UpdateViewOPRadio, self).dispatch(request, *args, **kwargs)
class DeleteViewOPRadio(DeleteView):
def dispatch(self, request, *args, **kwargs):
# Return 403 for demo user
temp = 'temp'
if self.request.user.email == 'demo#opradio.nl':
raise PermissionDenied
else:
return super(DeleteViewOPRadio, self).dispatch(request, *args, **kwargs)
Furthermore there are also some pages which should be inaccessible for which I used
from braces.views import UserPassesTestMixin
class UserNotDemoUser(UserPassesTestMixin):
raise_exception = True
def test_func(self, user):
return user.email != 'demo#opradio.nl'
What I tried
I created the following Views for pages where the form should be loaded but not saved when hitting the save button
class FormViewOPRadio(FormView):
def form_valid(self, form):
# Return 403 for demo user
if self.request.user.email == 'demo#opradio.nl':
raise PermissionDenied
else:
return super(FormViewOPRadio, self).form_valid(form)
class AddStream(LoginRequiredMixin, UserPassesTestMixin, SuccessMessageMixin, FormViewOPRadio):
"""Is the page used to add a Stream"""
template_name = 'opradioapp/addoreditstream.html'
form_class = AddStreamForm
success_url = reverse_lazy('opradioapp_home')
success_message = "De stream is opgeslagen"
# Validate if the user is the maintainer of the station
def test_func(self):
user = self.request.user
mainuserstation = MainUserStation.objects.get(slugname=self.kwargs['mainuserstationslug'])
if mainuserstation.maintainer == user:
return True
else:
return False
def form_valid(self, form):
user = self.request.user
mainuserstation = MainUserStation.objects.get(slugname=self.kwargs['mainuserstationslug'])
userstream = UserStream()
userstream.mainuserstation = mainuserstation
userstream.name = form.cleaned_data['name']
userstream.slugname = 'temp'
userstream.description = form.cleaned_data['description']
userstream.save()
member = Member.objects.get(user=user, mainuserstation=mainuserstation)
member.streamavailable.add(userstream)
member.save()
return super(AddStream, self).form_valid(form)
When doing it this way
if self.request.user.email == 'demo#opradio.nl':
raise PermissionDenied
is called after the save() calls. How can I change this? I tried calling super earlier but than I ran into problems.
Off course there are multiple ways of doing this that I know of. But they all require me to edit multiple pages or views:
Well, you won't have to repeat the logic for every template or view if you utilize certain DRY features of Python and Django.
Class Based View Inheritance
class CheckForDemoUser(View):
def dispatch(self, request, *args, **kwargs):
# check for demo user
# handle which ever way you see fit.
super(CheckForDemoUser, self).dispatch(request, *a, **kw)
class ChildClass(CheckForDemoUser): # notice inheritance here
def get(request, *args, **kwargs):
# continue with normal request handling
# this view will always check for demo user
# without the need to repeat yourself.
Function Decorators
def check_for_demo_user(func):
def func_wrapper(request, *args, **kwargs):
# implement logic to determine what the view should
# do if the request.user is demo user.
return func_wrapper
#check_for_demo_user
def my_view(request, *args, **kwargs):
# automatic checking happening before view gets to this point.
With Inclusion Tags you can isolate the logic of hiding/showing form submit buttons in one place and refer to your custom tag in multiple pages that the demo user would be on.
These are just some of the ways you can implement this logic without having to repeat yourself over and over.

Saving super() return value in Django's Mixins

I want to create DRY Django Mixins for things I do frequently. I have multiple Access Mixins that all need current Organization, so I wanted the code responsible for getting the Organization to be in separate Mixin, that also inherits from Djangos LoginRequiredMixin, because we will always check that if we're in organization page.
But instead of preforming dispatch method of inherited classes, saving the response and doing its thing, it skips it.
I started reading about Python's MRO and all, but I still can't find a way to make it all work.
Is my basic idea wrong? What is the best way to achieve this?
Thanks in advance,
Paweł
Code:
class OrganizationMixin(LoginRequiredMixin):
"""
CBV mixin for getting current organization.
Inherits from `LoginRequiredMixin` because we need logged in user to check
their membership and/or organization role.
"""
org_kwarg_name = 'organization_slug'
organization = None
def dispatch(self, request, *args, **kwargs):
response = super().dispatch(request, *args, **kwargs)
self.organization = self.get_organization()
return response
def get_organization(self):
# Get the slug from URL
slug = self.kwargs.get(self.org_kwarg_name, None)
return get_object_or_404(Organization, slug=slug)
class MembershipRequiredMixin(OrganizationMixin, AccessMixin):
"""
CBV mixin for checking if user belongs to current organization.
Already checks if user is logged in.
"""
permission_denied_message = _("You need to belong to the organization "
"to visit this page.")
def dispatch(self, request, *args, **kwargs):
response = super().dispatch(request, *args, **kwargs)
# Check if user is a member or a superuser
if (not self.organization.is_member(request.user) and
not request.user.is_superuser):
return self.handle_no_permission()
return response

How to access self.request.user in clean_avatar() in forms.py (django application)

On a Django form, I'm getting my users to upload an avatar. I have a clean_avatar method associated to the relevant ModelForm in forms.py, where I'm doing some processing on the submitted avatar before uploading it. How do I access self.request.user in this clean_avatar method? I tried over-riding __init__ of the ModelForm but goofed up.
For those interested, I want access self.request.user in the clean_avatar method in order to check whether the avatar being uploaded now is the same as the one the user submitted just before this. This will help me save bandwidth in cases where the form's being re-submitted (I've checked; in my app, re-submission causes the avatar to be re-submitted too, and re-processed).
The clean_avatar method currently looks like this:
def clean_avatar(self):
image=self.cleaned_data.get("avatar")
if image:
try:
if image.size > 1000000:
return None
except:
pass
image = Image.open(image)
image = MakeThumbnail(image)
return image
else:
return None
I think it's better to pass it to your form as an extra parameter so that you could use it throughout form:
class FooForm(models.ModelForm):
def __init__(self, *args, **kwargs):
# you take the user out of kwargs and store it as a class attribute
self.user = kwargs.pop('user', None)
super(FooForm, self).__init__(*args, **kwargs)
def clean_avatar(self):
current_user = self.user
# use current_user
Your views.py method:
def view(request):
foo_form = FooForm(request.POST or None, user=request.user)

django-allauth: Check whether user signed up using a social account

So I have integrated django-allauth in my app, and now users have the ability to log in via instagram. Let's say I have a model called UserProfile and it has a field
user_avatar = models.ImageField(upload_to='profile_images', blank=True, default=None)
And with that I have a signal that creates a user profile as soon as a new user registers:
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)
So usually when the user registers the user_avatar is blank since the default is set as None, now I want to add in the signal(if that's the correct way of doing it), to check if the user created his account via signing in using instagram, to go and fetch his profile picture and use it in the user_avatar. I think it's possible https://instagram.com/developer/endpoints/users/, but since I am a complete noob in python and django I don't know how to exactly do it.
So I found this signal from the django-allauth docs allauth.socialaccount.signals.pre_social_login(request, social_login) so this states that I can check that the user has signed up using a social account, but how would I use it with my create_user_profile function? The steps that I thought of is to first create the profile which I did and then to check whether the user signed up using a social account or not, if they did then the user_avatar which use their instagram profile picture and if not it would stay as none.
And as a plus I know that I can fetch the users social account profile picture in a template using {{user.socialaccount_set.all.0.get_avatar_url}}, but I don't want to do it via templates rather than doing it via Models which is the best way.
This might look really stupid but I gave it a go and tried to come up with something (this is what a newbie thinks would work, I thought this on top of my head, as I have no idea if this how signals work)
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
def pre_social_login(request, social_login):
user_logged_social = social_login.account.user
if user_logged_social:
UserProfile.objects.get(user_avatar=user_logged_social.profile_picture)
else:
pass
post_save.connect(create_user_profile, sender=User)
UPDATE
Got it working with the help of #bellum! Thank you!
Here is the code that I used:
models.py
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name="profile")
user_avatar = models.ImageField(upload_to='profile_images'
blank=True,
default=None)
def __unicode__(self):
return self.user.username
class Meta:
verbose_name_plural = "User Profiles"
def create_user_profile(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)
3utils.py
def download_file_from_url(url):
# Stream the image from the url
try:
request = requests.get(url, stream=True)
except requests.exceptions.RequestException as e:
# TODO: log error here
return None
if request.status_code != requests.codes.ok:
# TODO: log error here
return None
# Create a temporary file
lf = tempfile.NamedTemporaryFile()
# Read the streamed image in sections
for block in request.iter_content(1024 * 8):
# If no more file then stop
if not block:
break
# Write image block to temporary file
lf.write(block)
return files.File(lf)
class SocialAccountAdapter(DefaultSocialAccountAdapter):
def save_user(self, request, sociallogin, form=None):
user = super(SocialAccountAdapter, self).save_user(request, sociallogin, form)
url = sociallogin.account.get_avatar_url()
avatar = download_file_from_url(url)
if avatar:
profile = user.profile # access your profile from user by correct name
profile.user_avatar.save('avatar%d.jpg' % user.pk, avatar)
return user
settings.py
SOCIALACCOUNT_ADAPTER = 'main.s3utils.SocialAccountAdapter'
The signal for creating the profile on sign up in my models was left the same, just added an SocialAccountAdapter!
I have done the same task for Facebook provider. allauth gives possibility to achive this in another way. I think you don't need to get avatar every time user logins in your system. If yes then you can override class like this:
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
class SocialAccountAdapter(DefaultSocialAccountAdapter):
def save_user(self, request, sociallogin, form=None):
user = super(SocialAccountAdapter, self).save_user(request, sociallogin, form)
url = sociallogin.account.get_avatar_url()
avatar = download_file_from_url(url) # here you should download file from provided url, the code is below
if avatar:
profile = user.user_profile # access your profile from user by correct name
profile.user_avatar.save('avatar%d.jpg' % user.pk, avatar)
return user
You should add this line to your config: SOCIALACCOUNT_ADAPTER = 'path-to-your-adapter.SocialAccountAdapter'.
As result this code will be called only during new socialaccount registration process, fetch avatar url, download it and save in your User model.
import requests
import tempfile
from django.core import files
def download_file_from_url(url):
# Stream the image from the url
try:
request = requests.get(url, stream=True)
except requests.exceptions.RequestException as e:
# TODO: log error here
return None
if request.status_code != requests.codes.ok:
# TODO: log error here
return None
# Create a temporary file
lf = tempfile.NamedTemporaryFile()
# Read the streamed image in sections
for block in request.iter_content(1024 * 8):
# If no more file then stop
if not block:
break
# Write image block to temporary file
lf.write(block)
return files.File(lf)

Categories