2

tastypie0.9.12+でレコードレベルの認証を実装するのに問題があります。

私のセットアップは次のようになります。

モデル

class UserProfile(models.Model):
    def __unicode__(self):
        return self.user.get_full_name()

    user = models.OneToOneField(User)

class Sample(models.Model):
    def __unicode__(self):
        return '%s' % self.id

    OPEN = 0
    CLAIMED = 1
    CLOSED = 2
    MANUAL = 3
    MODIFIED = 4
    DELETED = 5
    ERROR = 6
    RESERVED = 7
    STATUS_CHOICES = (
        (OPEN, 'Open'),
        (CLAIMED, 'Claimed'),
        (CLOSED, 'Closed'),
        (MANUAL, 'Manual'),
        (MODIFIED, 'Modified'),
        (DELETED, 'Deleted'),
        (ERROR, 'Error'),
        (RESERVED, 'Reserved'),
    )

    status = models.SmallIntegerField(max_length = 1, default = OPEN, choices = STATUS_CHOICES)
    user_profile = models.ForeignKey(UserProfile, blank = True, null = True)

リソース

class BaseResource(ModelResource):
    # Base class with rather strict default settings
    # All other Resources extend this and override any defaults to higher permissions
    class Meta:
        authentication = DjangoAuthentication()
        authorization = ReadOnlyAuthorization()
        allowed_methods = []

class SampleResource(BaseResource): # BaseResource defines a default Meta, setting allowed_methods and authentication for all other resources in the API
    UserProfile = fields.ForeignKey(UserProfileResource, 'user_profile', null = True, full = True)
    class Meta(BaseResource.Meta):
        queryset = Sample.objects.all()
        resource_name = 'sample'
        allowed_methods = ['get', 'post', 'put', 'patch']
        authorization = SampleAuthorization()
        always_return_data = True

    def dehydrate_status(self, bundle):
        return Sample.STATUS_CHOICES[bundle.data['status']][1]

    def hydrate_status(self, bundle):
        bundle.data['status'] = Sample.__dict__[bundle.data['status'].upper()]
        return bundle

承認

class SampleAuthorization(Authorization):
    # Checks that the records' owner is either None or the logged in user
    def authorize_user(self, bundle):
        return bundle.obj.user_profile in (None, self.user_profile(bundle))

    def user_profile(self, bundle):
        return user_profile.objects.get(user = bundle.request.user)



    def read_list(self, object_list, bundle):
        print 'Read List'
        return object_list.filter(Q(user_profile = self.user_profile(bundle)) | Q(user_profile = None))

    def read_detail(self, object_list, bundle):
        print 'Read Detail'
        return self.authorize_user(bundle)

    def create_list(self, object_list, bundle):
        return object_list

    def create_detail(self, object_list, bundle):
        return self.authorize_user(bundle)

    def update_list(self, object_list, bundle):
        print 'Update List'
        allowed = []
        for obj in object_list:
            if obj.user_profile in (None, self.user_profile(bundle)):
                allowed.append(obj)

        return allowed

    def update_detail(self, object_list, bundle):
        print 'Update Detail'
        print bundle.obj.status, bundle.data['status']
        # Compare status stored on the server against the user-set status
        # If server status is >= user status
        # Raise Unauthorized
        if bundle.obj.status >= bundle.data['status']:
                raise Unauthorized('New status must be higher than current status')
        return self.authorize_user(bundle)

    def delete_list(self, object_list, bundle):
        raise Unauthorized('Deletion not allowed through API')

    def delete_detail(self, object_list, bundle):
        raise Unauthorized('Deletion not allowed through API')

私の問題は、update_detailが異なる入力で2回呼び出されているように見えることです。要求された更新は、サーバーに保存されているレコードのステータスを変更しようとしています。新しいステータスは、保存されているステータスよりも高い必要があります。そうでない場合、変更は未承認です。

上記のコードを実行すると、出力は次のようになります。

Read Detail
Update Detail
0 Claimed
Update Detail
1 1
[27/Mar/2013 09:35:23] "PATCH /api/1.0/sample/1/ HTTP/1.1" 401 0

最初のパスでは、bundle.obj.statusの値は正しいですが、bundle.data['status']はハイドレイトされていません。2回目のパスで、bundle.obj.statusが新しいステータスに変更され、新しいステータスがハイドレイトされました。

最初のパスでステータスがハイドレイトされていないため、それらを確実に比較することはできません。また、バックグラウンドで実行されるハイドレートプロセス全体を台無しにするため、手動でhydrate_statusを呼び出したくありません。2番目のパスの値は同じであるため、どのステータスに設定しても、常にUnauthorized例外が発生します。

保存されたステータス値と新しいステータス値の両方に対して異なる入力を使用してメソッドがTastypieによって2回呼び出された場合、レコードレベルの承認を実装するにはどうすればよいですか?

4

2 に答える 2

1

結局のところ、update_detailへの複数の呼び出しは、tastypieフレームワークのバグでした。

問題はgithubで送信され、バグ修正で解決されました。

于 2013-04-28T15:30:08.367 に答える
0

モデルに関係をハードコーディングする代わりに、django-guardianを使用する方法をご覧ください。次の承認クラスのようなものが良いスタートです:

https://gist.github.com/airtonix/5476453

于 2013-04-28T09:59:33.690 に答える