I need to allow administrators to manage permissions for models on my site. Groups, Users, and Permissions are doing a great job of this right now. However, I also need to allow the administrators to manage the permissions of non-authenticated users - Anonymous Users. The docs say that anonymous user's group is always empty, so how can I allow administration of their permissions?
It is strange to add permissions to anonymous users. Docs say:
Django's permission framework does not have a place to store
permissions for anonymous users. However, it has a foundation that
allows custom authentication backends to specify authorization for
anonymous users. This is especially useful for the authors of
re-usable apps, who can delegate all questions of authorization to the
auth backend, rather than needing settings, for example, to control
anonymous access.
So you can set permissions to anon yuser, but with custom auth backend.
It is sometimes better to use declarative permission check, using decorators on the views with the needed permissions, like:
#permission_required('somemodel.can_add')
def add_model(request):
or leave it unrestricted for everyone(incl. anonymous user). Or some custom permission check..
Or if you want to have permissions anyway, you can always create a dummy user, let's say "AnonUser", to give it permissions, and then checking permissions to have something like:
if not user.is_authenticated():
dummy_user = User.objects.get(name="AnonUser")
if dummy_user.has_perm("somepermission"):
# bla bla bla
but this is something I'd never use..
I've gone with a custom backend to provide an anonymous group (as #Tisho suggests), which can easily be edited from the admin interface. Add the following class to AUTHENTICATION_BACKENDS in settings.py (e.g. 'appname.auth_backend.AnonymousPermissions').
Note that it's common to require a login before checking permissions (e.g. with login_required), which will have to be replaced with permission only checks.
from django.contrib.auth.models import Group
class AnonymousPermissions(object):
def get_anonymous_permissions(self):
#largely from django.contrib.auth.backends
group, group_created = Group.objects.get_or_create(name='Anonymous')
perms = group.permissions.all()
perms = perms.values_list('content_type__app_label', 'codename').order_by()
perms = set("%s.%s" % (ct, name) for ct, name in perms)
return perms
def get_group_permissions(self, user_obj, obj=None):
if user_obj.is_anonymous:
perm_cache_name = '_anonymous_perm_cache'
if not hasattr(user_obj, perm_cache_name):
setattr(user_obj, perm_cache_name, self.get_anonymous_permissions())
return getattr(user_obj, perm_cache_name)
return set()
def get_all_permissions(self, user_obj, obj=None):
return self.get_group_permissions(user_obj, obj)
def has_perm(self, user_obj, perm, obj=None):
return perm in self.get_group_permissions(user_obj, obj)
Related
I am working on a third party app running django 2.2.12.
I have an admin with 3 kinds of users: "superadmin", "staff" and "other".
When I am logged as "superadmin", when I go to the Users list and click on a user, I am able to edit them and reset their password.
However, when I try to do the same as a "staff" user, I can see the user data, but can't edit anything, and there's no option to reset the password. If I try to access the URL manually (/admin/accounts/user/[USER_ID]/password/) I get a "403 Forbidden" error message.
I noticed that if I override the has_perm method in the user model, it allows the "staff" user to edit the user data. However I would like to allow only the password change (if the user is not a superadmin or a staff user), without allowing "staff" users to edit other users.
def has_perm(self, perm, obj=None):
return True
I have the impression this has to do with the Django permission system, but I re-read the docs at https://docs.djangoproject.com/en/2.2/topics/auth/default/ and couldn't figure this out yet.
Thanks
Okay, I was able to make it work with a less than ideal solution, but did the trick.
Basically I overload the has_change_permission method of the UserAdmin class and add a custom logic to allow the password change in a special case (url ends with '/password' and user has the correct permissions.
def can_edit_password(self, logged_user, chosen_user=None):
if logged_user and logged_user.is_superuser:
return True
logged_user_has_change_pass_perm = (
logged_user and
logged_user.is_authenticated and
logged_user.has_perm("accounts.change_student_password")
)
return logged_user_has_change_pass_perm
def can_show_change_password_form(self, path, logged_user, chosen_user=None):
is_changing_password = path.endswith('/password/')
return is_changing_password and self.can_edit_password(logged_user, chosen_user)
def has_change_permission(self, request, user=None):
if self.can_show_change_password_form(path=request.path, logged_user=request.user, chosen_user=user):
return True
return super(UserAdmin, self).has_change_permission(request, user)
If I am logged in as user1 and I am accessing a ViewSet called RecipeSubmissionViewSet, the POST takes a recipe_id of the recipe the user wants to submit. How do I ensure that user1 does not submit user2's recipe, assuming the Recipe model has an owner field on it and I can just compare it to request.user? Should I use a permission class for this or is there a better way? I'm speaking from an backend point of view and not taking into account that the front end would of course filter out the recipes that belong to the user and only show them their own recipes.
There can be two ways. You can filter out queryset or define permission class.
If you override get_queryset method like this.
class RecipeSubmissionViewSet(...):
def get_queryset(self):
return Recipe.objects.filter(owner=self.request.user)
# you can also use filtration based on action name like this
# if self.action == 'update':
# return Recipe.objects.filter(owner=self.request.user)
# return Recipe.objects.all()
User will get 404 response and will never be able to access objects other than he owns.
Second choice is permission class. You can define custom permission class and check ownership explicitly like this.
from rest_framework.permissions import BasePermission
class RecipeSubmissionPermission(BasePermission):
def has_object_permission(self, request, view, obj):
# you can also check permission here based on action
# if view.action == 'update':
# pass
return request.user.is_authenticated and obj.owner == request.user
class RecipeSubmissionViewSet(...):
permission_classes=[RecipeSubmissionPermission]
In this case user will get 403 permission error.
If you use both of these methods. 404 will be preferred.
You can use whichever method you want or both of these. Permission class looks more programmatic and structured way of doing it but user will know that object with this id exists but he did not have permission to update it. But if you override queryset, user is not even be able to know if object exists or not thus more secure.
Are you using the django authentication system? Then you should be able to access request.user in the views and set the owner field accordingly.
EDIT: I think I misunderstood the question.
But this could help and Nafees Anwar looks good.
Nafees answer is pretty much the way to go.
I have been developing microservices with multitenancy to users and the rules for them(as per my projecs spec) are:
Users cannot create items on behalf of another company/user
Users cannot view/edit items belonging to another company.
The way I do this is simple.
To prohibit viewing/editing of someone else's stuff
def get_queryset(self):
return self.queryset.filter(user=request.user)
And to prohibit editing of someone else's stuff, this is done on a serializer
class SomeSerializer(...):
def validate(self, data):
data.pop('user', None)
data['user'] = self.context['request'].user
With the above, the get_queryset in the viewset will always return a 404 if user1 requests user2 info.
And the validate function will prevent assignment. Ie. If user1 creates something assigned to user2, it will rather assign user1, this behaviour is what I needed in my application, you can always raise serializers.ValidationError("You cannot assign stuff to user2") instead if thats what you need instead of reassigning the user as I do in my use case. With having this logic in the validate you can be sure that any writable function will always carry the same behaviour for that serializer.
Hope that this helps.
I want to have authority control over my project. Some users can see some app's entries while others can't. I want to set permissions on apps instead of models, I have searched, but only found how to set permissions on models. So, I want to know how to set permissions on apps.
You can make decorators to selectively allow users to access the page
Make this decorator
def filter_users(func):
def checker(request,*args,**kwargs):
if some_condition: #This condition will tell you whether to allow this perticular user
return func(request,*args,**kwargs)
else:
return render('invalid.html') #return a page telling the user that he is not allowed
return checker
Now just apply this decorator to all the views that you want to prevent 'some' users from accessing.
Ex:
#filter_users
def some_view(request):
#Do Something...
Now only allowed users will be able to see the view, rest all will get the invalid page
You can apply this decorator to all the views of the perticular app that you want to restrict access to
In my Django REST api, I have two types of users, provider and consumer, each is one-to-one linked with user object from django.contrib.auth. I want to make the consumer profiles available to all provider users to read but not to consumer users. The only consumer to read the profile is the owner, who should be able to edit it too.
I want to make a permission class
class ConsumerProfilePermission(BasePermission):
'''
if provider -> read only
if owner or admin -> full access
if other consumer -> permission denied
'''
so I won't have to check the permission in the view. How should I go about this?
You need to use object_permission level. I think this should work:
class ConsumerProfilePermission(BasePermission):
def has_object_permission(self, request, view, profile):
if request.method in permissions.SAFE_METHODS:
// Your Instance of profile must have an attribute named `owner` or replace it with the respective attribute.
return (request.user.is_provider) or (request.user == profile.owner) or request.user.is_staff
else:
return request.user == profile.owner or request.user.is_staff
If you use generic views, then they will check the appropriate object level permissions, but if you're writing your own custom views, you'll need to make sure you check the object level permission checks yourself. here in the docs explain this
I have created a list of 5 users. How do I find out which user has logged in currently? Also please mention, if there is any way to find out if the super-user has logged in?
My requirement is, I want to restrict the access of certain pages in the templates only to the superuser.
Current user is in request object:
def my_view(request):
current_user = request.user
It's django.contrib.auth.models.User class and it has some fields, e.g.
is_staff - Boolean. Designates whether this user can access the admin site;
is_superuser - Boolean. Designates that this user has all permissions without explicitly assigning them.
http://docs.djangoproject.com/en/1.1/topics/auth/#django.contrib.auth.models.User
So to test whether current user is superuser you can:
if user.is_active and user.is_superuser:
...
You can use it in template or pass this to template as variable via context.
Sounds like you should be using the built-in permissions system for this.
Check out the user_passes_test decorator for your views. Django snippets has a related decorator:
These decorators are based on
user_passes_test and
permission_required, but when a user
is logged in and fails the test, it
will render a 403 error instead of
redirecting to login - only anonymous
users will be asked to login.
http://www.djangosnippets.org/snippets/254/