How to restrict user's permission to only modify their own info/data?

82 阅读1分钟

First let impliment user can only modify their own user data

Because we are using django auth.model User so we can add a custom permission:

class UserCanOnlyModifyHimSelfPermission(permissions.BasePermission):
    def has_permission(self, request, view):
        # watchout view.kwargs.get('pk') is a str because we are use it as a router
        return request.method in SAFE_METHODS or (
                request.user.is_authenticated and int(view.kwargs.get('pk')) == request.user.id)


class UserViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, viewsets.GenericViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly, UserCanOnlyModifyHimSelfPermission]

Test Case:

class UserTestCase(TestCase, TransactionTestCase):
    databases = {'default'}

    def setUp(self):
        from django.core.management import call_command
        call_command('migrate', verbosity=0, interactive=False, database='default')
        self.defaultUserName = 'defaultUser'
        self.defaultPassword = "defaultUserPassword"
        self.defaultUser = User.objects.create_user(username=self.defaultUserName, password=self.defaultPassword)

        self.defaultUserName2 = 'defaultUser2'
        self.defaultPassword2 = "defaultUserPassword2"
        self.defaultUser2 = User.objects.create_user(username=self.defaultUserName2, password=self.defaultPassword2)

    def test_can_only_modify_self_info(self):
        data = {"username": "userForTestToken"}
        # before register shouldn't modify user's info
        response = self.client.patch(f'/user/{self.defaultUser.id}/', data, )
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

        # set header to mock login
        token, created = Token.objects.get_or_create(user=self.defaultUser)
        client = APIClient()
        client.credentials(HTTP_AUTHORIZATION='Token ' + token.key)

        # after login can modify
        response = client.patch(f'/user/{self.defaultUser.id}/', data, )
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        # cannot modify other's info
        response = client.patch(f'/user/{self.defaultUser2.id}/', data, )
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

        # Stop including any credentials
        client.credentials()

Another way to implement

image.png


User can only modify themselves data

TLDR: add a owner field.

class Book(models.Model):
    title = models.CharField(max_length=100)
    owner = models.ForeignKey('auth.User', on_delete=models.CASCADE)
    
class BookSerializer(serializers.ModelSerializer):
    # This will only return User.username
    # owner = serializers.ReadOnlyField(source='owner.username')
    # This will return full User info
    # BUT it will cause owner field is required when deserialized.so we must declare read_only=True
    owner = UserSerializer(read_only=True)

    class Meta:
        model = Book
        fields = '__all__'

Test Case:

class BookTestCase(TestCase, TransactionTestCase):
    databases = {'default'}

    def setUp(self):
        from django.core.management import call_command
        call_command('migrate', verbosity=0, interactive=False, database='default')

        # make sure we have a default user.
        self.defaultUserName = 'bookCreator'
        self.defaultPassword = "defaultUserPassword"
        self.defaultUser = User.objects.create_user(username=self.defaultUserName, password=self.defaultPassword)

        self.defaultUserName2 = 'bookCreator2'
        self.defaultPassword2 = "defaultUserPassword2"
        self.defaultUser2 = User.objects.create_user(username=self.defaultUserName2, password=self.defaultPassword2)
        
    def test_user_can_only_modify_himself_book(self):
        client = APIClient()
        token1, created1 = Token.objects.get_or_create(user=self.defaultUser)
        token2, created2 = Token.objects.get_or_create(user=self.defaultUser2)

        # first we log in user1
        client.credentials(HTTP_AUTHORIZATION='Token ' + token1.key)
        # let user1 create book1
        response = client.post(path='/book/', data={'title': 'book1'})
        book1_id = response.data['id']
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(response.data['owner']['id'], self.defaultUser.id)
        # user1 can modify book1
        response = client.patch(path=f'/book/{book1_id}/', data={'title': 'book1-user1-modify'})
        self.assertEqual(response.status_code, status.HTTP_200_OK)

        # login user2
        client.credentials(HTTP_AUTHORIZATION='Token ' + token2.key)
        # user2 cannot modify book1
        response = client.patch(path=f'/book/{book1_id}/', data={'title': 'book1-user2-modify'})
        self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)

        # at last, we check book1 are not modified by user2
        self.assertEqual(
            Book.objects.get(title='book1-user1-modify').owner.id, self.defaultUser.id
        )
        # Stop including any credentials
        client.credentials()

Thanks for reading.