I'm adding django-braces to a system to ensure only certain users can perform certain actions. As a background, I've got some users who login and potentially have other users associated with their account, like a team leader.
Those team leaders are able to edit the details of their team so I've got a UserPassesTestMixin on that edit view and a LoginRequiredMixin on the dashboard view once they login.
Here are my views;
class Dashboard(DetailView, LoginRequiredMixin):
template_name = 'online_entry/entrant/dashboard.html'
model = FreeCycleEntrant
http_method_names = ['get']
class UpdateEntrantWizard(SignupWizard, UserPassesTestMixin):
template_name = 'online_entry/entrant/wizard_form.html'
form_list = [EditTeamMember]
model = Entrant
instance = None
def get_instance_obj(self):
if self.instance is None and 'pk' in self.kwargs:
self.instance = get_object_or_404(
Entrant, pk=self.kwargs['pk']
)
return self.instance
def test_func(self, user):
parent = Entrant.objects.get(user=user)
return self.get_instance_obj().user_has_access(user, parent=parent)
And the test function on my Entrant model is quite straightforward;
def user_has_access(self, user, parent=None):
"""
Indicates whether or not a user can edit this user. The passed in user
is the logged in user.
:type self: object
"""
if self.user == user:
return True
if isinstance(user, AnonymousUser):
return False
if parent and self.parent_entrant == parent:
return True
return False
So when I'm logged in as one of these users, and from the Dashboard attempt to go to that UpdateEntrantWizard view I'm redirected to /accounts/profile/ before the tests are hit (using breakpoints).
Am I missing something in my implementation of braces? It looks like you really only need to add the test & the mixin to get things working.
UserPassesTestMixin inherits from AccessMixin, which includes the login_url property. Simply set:
login_url = "/your_url/"
and it will redirect to a url of your choice.
As far as testing is concerned you can use the django Client. The example below from an app of mine should give the idea here. Obviously the user 'Claire' in this test should be created where the ellipses are, and with the permissions needed to pass the test.
def test_home_url_allows_authenticated_user(self):
c = Client()
...
c.login(username='Claire', password='pass')
content = c.get('/travel/')
self.assertEqual(content.status_code, 200)
Finally you may want to add a message in your login page along these lines:
{% if user.is_authenticated %}
<h2>You're logged in, but you might not have permission to view the page you requested.</h2>
{% else %}
...
Because that's where users will end up if they don't have the right permissions.
Try switching
class UpdateEntrantWizard(SignupWizard, UserPassesTestMixin):
To
class UpdateEntrantWizard(UserPassesTestMixin, SignupWizard):
When using something like :
class SomeProtectedView(views.LoginRequiredMixin,
views.PermissionRequiredMixin,
TemplateView):
the order in which you call the mixins matter. In this example it will check if user is logged in before checking permissions. And, if you were to put TemplateView first it would bypass the mixins altogether.
Related
so I have some code which creates a user but how do I make sure every url that the user goes to will be in his/her own user session such that request.user will be the user I created?
def liked(request):
try:
sp = Spotify(auth_manager=oauth)
liked = sp.current_user_saved_tracks(limit=30)['items']
spotify_user = sp.current_user()
user__ , created =
User.objects.get_or_create(username=spotify_user['uri'],
first_name=spotify_user["display_name"])
userprofile = UserProfile.objects.get_or_create(user=user__)[0]
i have other views i want only accessible to the user created or gotten from the get_or_create, i reckon I'd have to use the #login_required decorator but then again I do not know what constitutes this "login" with respect to the user I created. How do I do ensure that user is the logged in user?
Django's default MIDDLEWARE takes care of this for you.
In every request object or self.request there is a user object.
Django checks the session via the cookie that the browser sends with every request, and puts the user object in the current request.
An example in a Class based view:
class ExampleView(TemplateView):
template = 'example.html'
def get_context_data(self, **kwargs):
context = super(ExampleView, self).get_context_data(**kwargs)
user = self.request.user
users_data = ExampleData.objects.filter(user=user)
Once a user is logged in, every other page is put into the session. Just log that user in first.
If you want to check if the user is logged in
In class based views;
if self.request.user.is_authenticated
In function based view
if request.user.is_authenticated
Ok as i understand you need to limit some users to access some pages and not for others .. i suggest to use permissions way
add permission to any Model you Have Like this
then when you create your special user do this to assign this permission to him like that : myuser.user_permissions.add('can_view_all_clients', 'another_perm', ...)
For Function Based Views :
from django.contrib.auth.decorators import permission_required
#permission_required("YourModel.can_view_all_clients")
def your_special_view(request):
return 'your view'
For Class Based View :
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.views.generic import DetailView
class ModelDetailView(PermissionRequiredMixin, DetailView):
template_name = "model_detail.html"
model = Model
permission_required = "can_view_all_clients"
I have one model which has user as its ForeignKey attribute which is auto fill ie. logged in user is filled there. I have made token authentication. Only Authenticated // i mean authorized users can visit that view. But i am planning to make such that only the user which had created that model object can only update the content of that object.
For example:
class Something(models.Model):
sth_name = models.CharField(max_length=18)
sth_qty = models.IntegerField()
user = models.ForeignKey(User)
on my View:
I override perform_create() to associate to above model automaticall.
def perform_create(self, serializer):
return serializer.save(user=self.request.user)
What do i exactly need to do? I have to write some permissions method, But I am really stuck.
Yes, you need to create an object level permission. The DRF tutorial covers this nicely here: http://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/#object-level-permissions
Specifically, create a file permissions.py in your app, and add this permission there:
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
"""
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.user == request.user
Then, in your view class which has the update resource for the Something model (probably SomethingDetail), add the permission_classes field:
class SomethingDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Something.objects.all()
serializer_class = SomethingSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
Just add the user when retrieving the object
obj = get_object_or_404(Something, pk=pk, user=request.user)
Note that this will throw 404. If you want 403 error, use custom condition to check the user and raise PermissionDenied. If you want to do this for multiple views, put the condition logic in a decorator.
I'm a new django user looking for some help with django-rules. I'm trying to set up an authorization system that is 'OR' based. I have a 'File' model. I would like only the creator to be able to delete it, but a specific set of users to edit it. I've been able to followed their tutorial and implementation throughout; it works in the shell but not on my site. At the moment no one can delete or update anything.
My view currently looks like:
class FileUpdateView(PermissionRequiredMixin, generics.RetrieveUpdateAPIView):
"""
View updating details of a bill
"""
queryset = File.objects.all()
serializer_class = FileSerializer
permission_required = 'fileupload.change_file'
raise_exception = True
class FileDeleteView(PermissionRequiredMixin, generics.RetrieveDestroyAPIView):
"""
View for deleting a bill
"""
queryset = File.objects.all()
serializer_class = FileSerializer
permission_required = 'fileupload.delete_file'
raise_exception = True
The rules themselves are:
import rules
#rules.predicate
def is_creator(user, file):
"""Checks if user is file's creator"""
return file.owner == user
is_editor = rules.is_group_member('ReadAndWrite')
rules.add_perm('fileupload.change_file', is_editor | is_creator)
rules.add_perm('fileupload.delete_file', is_creator)
I know I'm close I feel like I'm just missing one step.
Thanks in advance!
please check & add settings file authentication backend for Django-rules. Also, you are mixing Django rest permissions with Django-rules permission. you need to check Django-rules permission in Django-rest permissions on view.
in short.
define a custom permission in rest-framework like this.
from rest_framework import permissions
class RulesPermissions(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return request.user.has_perm('books.edit_book', obj)
in viewset.
class BookView(viewsets.ModelViewSet):
permission_classes = (RulesPermissions,)
I've been working with the django REST framework and django-rules for a project and found an answer to your question.
The django REST framework uses an API view which is not compatible with rules' views.PermissionRequiredMixin, the authorization workflow and methods called during the API dispatch is different from django's class based view.
Try the following mixin for django REST framework API views and their subclasses:
import six
from django.core.exceptions import ImproperlyConfigured
class PermissionRequiredMixin:
permission_required = None
def get_permission_object(self):
object_getter = getattr(self, 'get_object', lambda: None)
return object_getter()
def get_permission_required(self):
if self.permission_required is None:
raise ImproperlyConfigured(
'{0} is missing the permission_required attribute. Define '
'{0}.permission_required, or override '
'{0}.get_permission_required().'
.format(self.__class__.__name__)
)
if isinstance(self.permission_required, six.string_types):
perms = (self.permission_required, )
else:
perms = self.permission_required
return perms
def check_permissions(self, request):
obj = self.get_permission_object()
user = request.user
missing_permissions = [perm for perm in self.get_permission_required()
if not user.has_perm(perm, obj)]
if any(missing_permissions):
self.permission_denied(
request,
message=('MISSING: {}'.format(', '.join(missing_permissions))))
With this mixin you're not forced to write a REST framework permission for each rules permission.
I have something strange going on that I can't seem to crack. I'm building an API with Tastypie and when I issue this call in my browser against localserver, it works fine: localserver/api/v1/userfavorite/?user__username=testowner
However, in my code, I'm getting an error: "int() argument must be a string or a number, not 'SimpleLazyObject'". I realize it has to do with the user being treated as a request.user object, but I can't figure out where/why. I'm very confused why it works when issuing the API call in the browser, but in the code it is not working.
Here is my code:
# views.py
#login_required
def favorites(request):
'''
display a list of posts that a user has marked as favorite
'''
user = request.user
favorites_url = settings.BASE_URL + "/api/v1/userfavorite/?user__username=" + user.username
favorites = get_json(favorites_url)
return render(request, "maincontent/favorites.html", {'favorites':favorites})
# resources.py
class UserFavoriteResource(ModelResource):
'''
manage post favorites by a user. Users can use a favorites list
to easily view posts that they have liked or deemed important.
'''
user = fields.ForeignKey(UserResource, 'user')
post = fields.ForeignKey('blog.api.resources.PostResource', 'post', full=True)
class Meta:
queryset = UserFavorite.objects.all()
allowed_methods = ['get', 'post', 'delete']
authentication = Authentication()
authorization = Authorization()
filtering = {
'user':ALL_WITH_RELATIONS
}
def hydrate_user(self, bundle):
# build the current user to save for the favorite instance
bundle.data['user'] = bundle.request.user
return bundle
def get_object_list(self, request):
# filter results to the current user
return super(UserFavoriteResource, self).get_object_list(request)\
.filter(user=request.user)
# utils.py
def get_json(url):
# return the raw json from a request without any extraction
data = requests.get(url).json()
return data
Some notes:
1. I have the post method working to create the UserFavorite item
2. I can verify that the favorites_url is being generated correctly
3. I have tried hardcoding the favorites_url as well, same error.
EDIT: 4. I am logged in while doing this, and have verified that request.user returns the user
This doesn't work because there is Anonymous user in your request.user. You are using Authentication it does not require user to be logged in. So if you perform requests call that request is not authenticated and request.user is AnonymousUser and that error occurs when you try to save Anonymous user to db. Tastypie documentation advices to not using browsers to testing things up, just curl instead. Browsers stores a lot of data and yours one probably remembered you have been logged to admin panel in localhost:8000 on another tab that's why it worked in browser.
I would prefer something like this:
def hydrate_user(self, bundle):
"""\
Currently logged user is default.
"""
if bundle.request.method in ['POST', 'PUT']:
if not bundle.request.user.is_authenticated():
raise ValidationError('Must be logged in')
bundle.obj.user = bundle.request.user
bundle.data['user'] = \
'/api/v1/userauth/user/{}'.format(bundle.request.user.pk)
return bundle
I have this page :8000/edit/6/ that show a form to update an exciting model and i logged in as X, if i looged in as Y and try to open that page i can see it and update. So this is with no doubt a big bug and dangerous.
Here is my view code
class VideoUpdate(UpdateView):
form_class = VideoForm
model = Video
template_name = 'videos/video_update.html'
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(VideoUpdate, self).dispatch(*args, **kwargs)
def form_valid(self, form):
messages.info(self.request, _('Event is updated successfully'))
return super(VideoUpdate, self).form_valid(form)
Is there a way to check the model object id with the user id. A simple question from a newbie
Solution:
Actually there are two solutions that works for me in views.py, one is using the get_queryset method
def get_queryset(self):
base_qs = super(VideoUpdate, self).get_queryset()
return base_qs.filter(user=self.request.user.get_profile)
or using get_object method
def get_object(self):
video = get_object_or_404(Video, pk=self.kwargs['pk'])
if video.user != self.request.user.get_profile():
raise Http404
return video
Your question is not entirely clear to me but I think you want to restrict a view from registered but unauthorized users. Usually, this can be better achieved in your views instead of your models:
# views.py
def edit_form(request, parameter_indicating_user):
user = request.user
if #some logic:
# if user is equal to the user indicated by some parameter
# (id, username, etc) then allow that view to be rendered
else:
raise Http404 # or redirect the unauthorized user
I am interpreting your question as meaning the following.
When using class-based views - is there a way to control whether the specific instance of a form is editable by a given user ie. we have record #1 of a model. When you are logged in as user X you can edit record #1, but user Y is not allowed to edit record #1.
If this is what you are talking about you are going to require row/object level permissions, which I have found is best when using django-guardian.
Specifically, when using class-based views you can use the PermissionRequiredMixin, which can be found here: http://packages.python.org/django-guardian/api/guardian.mixins.html#permissionrequiredmixin
If you are looking for just controlling whether User X vs. User Y can edit any instance of that form. ie. User X can edit Form A values. Then you will just need to manage the permissions appropriately and then check if the user has that permission in the view.
JD