Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Problems with ForeignKey using POST in Django-Tastypie

I'm building a simple API using django-tastypie. The idea is I have two resources:

  • A Note resource representing a note left by a user. Only the user who created a Note can edit it.
  • A Comment resource. Comments can be left on any note by any user.

TL;DR: I am unable to limit Note editing to a Note's creator while still allowing any user to comment on a Note.

I am using the following setup for Authentication:

class CreatedByEditAuthorization(Authorization):
    def is_authorized(self, request, object=None, **kwargs):
        return True

    def apply_limits(self, request, object_list):
        if request and request.method != 'GET' and hasattr(request, 'user'):
            return object_list.filter(created_by=request.user)
        return object_list

In short, a user is only authorized to edit objects for which they are equal to the created_by property (they can only edit the objects they created).

This is linked as follows:

class NoteResource(ModelResource):
    comments = fields.ToManyField('myapp.api.resources.CommentResource', 'comments', null=True, blank=True)
    created_by = fields.ToOneField('account.api.resources.UserResource', 'created_by')

    def obj_create(self, bundle, request, **kwargs):
        return super(HapResource, self).obj_create(bundle, request, created_by=request.user)

    class Meta:
        queryset = Note.objects.all()
        allowed_methods = ['get', 'put', 'post']
        authorization = CreatedByEditAuthorization()

so here, when an object is created, I automatically attach the current user to the created_by attribute and link it to the proper Authorization.

A Comment resource is simple and just has a ForeignKey to a Note resource.

The problem is this: if user A creates a Note and user B attempts to comment on that Note, tastypie sends (or simulates) a POST request to edit that Note. That attempt is rejected as user B did not create the Note, so creating the comment fails.

The question is this: Is there a way to either:

  1. Prevent tastypie from using a POST to create the reverse-relation to the Note resource or
  2. Change the Authorization scheme so Notes can only be edited by their creator, but comments can be created generally?

Thanks in advance for any insights.

Edit: I have a big fat hack that can accomplish this. I'm fairly sure it's safe, but I'm not positive; I'll try constructing some queries to make sure. Instead of using fields.ForeignKey in Comment to relate to Note, I create a custom field:

class SafeForeignKey(fields.ForeignKey):
    def build_related_resource(self, value, request=None, related_obj=None, related_name=None):
        temp = request.method
        if isinstance(value, basestring):
            request.method = 'GET'
        ret = super(SafeForeignKey, self).build_related_resource(value, request, related_obj, related_name)
        request.method = temp
        return ret

Each time we try to construct this related resource, we mark the request as a GET (since we expect it to be matched to a SELECT query rather than an UPDATE which matches to PUT or POST). This is really ugly and potentially unsafe if used incorrectly, and I'm hoping for a better solution.

Edit 2: From reading the tastypie source, as far as I can tell there is no way to filter authorization by the query that will actually get sent.

like image 594
Alex Churchill Avatar asked Dec 26 '11 21:12

Alex Churchill


1 Answers

As per discussion on https://github.com/toastdriven/django-tastypie/issues/480#issuecomment-5561036:

The method that determines if a Resource can be updated is can_update. Therefore, to make this work in the "proper" way, you need to create a subclass of NoteResource:

class SafeNoteResource(NoteResource):
    def can_update(self):
        return False
    class Meta:
        queryset = Note.objects.all()
        allowed_methods = ['get']
        authorization = Authorization()
        # You MUST set this to the same resource_name as NoteResource
        resource_name = 'note'

then let CommentResource link to notes in the standard way: note = fields.ForeignKey(SafeNoteResource, 'note').

like image 140
Alex Churchill Avatar answered Oct 23 '22 13:10

Alex Churchill