본문 바로가기
Django

[Django] DRF - Permission

by 혀넝 2022. 5. 28.

Permission

어떤 콘텐츠에 대한 추가 또는 접근 관련 요청에 대해서 제한(restrictions)을 두게 하는 것

Permission의 세 종류

  • 1st type: global permission class -> permission을 settings.py에 추가해 모든 view class에 대해서 힘을 행사할 수 있게 한다.
  • 2nd type: object level permission → 특정 view class에 restriction 추가
    • several permission class: 위의 두 개의 permission level에서 사용되는 class
      • AllowAny: anyone can access any contents
      • IsAuthenticated: user should be allowed to access some particular contents, only login-user can access
      • IsAdminUser: only admin can access
      • IsAuthenticatedOrReadOnly: authenticated user can edit and others can read
  • 3rd type: custom permissions (아래에서 다시 설명)
    • 위와 같은 permission class를 사용하지 않고 custom 한 permission을 사용하기 위해서는 BasePermission을 override 하고 아래의 두 method 중 하나 또는 두 개를 사용해야 한다.
    • .has_permission(self, request, view): 이걸 사용하면 특정 object를 specifically testing 및 checking 할 수 있다.
      • ex) 한 리뷰는 해당 리뷰를 작성한 사람만 접근해서 수정할 수 있다. 이때 has_object_permission을 사용할 수 있다.
    • .has_object_permission(self, request, view, obj): user가 read 또는 다른 것에 대해 permission을 가지고 있는지 일반적으로 checking 한다.
    • 위 두 method는 request가 access 될 때 True를 반환하고 그렇지 않으면 False를 반환한다.
    • 해당 request가 read 또는 write와 관련이 있는지 테스트할 필요가 있으면 constant SAFE_METHOD로 체크하는 과정이 필요한데, 이것은 GET, OPTION, HEAD를 담고 있는 튜플이다.

 

global permission class

# settings.py

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ]
}
  • IsAuthenticated 사용: login user 상태가 되어야 접근 가능함을 의미
  • 다른 level의 permission을 사용하고 싶으면, IsAuthenticated 자리에 위에 있는 IsAdminUser 등을 대신 입력하면 된다.
    • ex) 'rest_framework.permissions.IsAdminUser',
  • 따라서 로그인하지 않은 경우 아래와 같은 에러를 반환한다
HTTP 403 Forbidden
...

{
    "detail": "Authentication credentials were not provided."
}

settings.py에 DEFAULT_PERMISSION_CLASSES를 추가하면 일괄적으로 permission을 부여할 수 있지만, view class마다 다른 condition을 추가하고 싶을 때에는 유용하지 않다.

 

object level permission

CBV를 사용하는 경우, view class 내부에 permission_class를 지정하면 되고, FBV를 사용하는 경우는 decorator를 사용하면 된다.

# views.py

...
from rest_framework.permissions import IsAuthenticated
...

class ReviewList(generics.ListAPIView):
    serializer_class   = ReviewSerializer
    permission_classes = [IsAuthenticated]

...

login 하지 않고 review list에 접근하려고 하면 1번에서 본 에러가 발생한다.

 

custom permission

permissions.py를 따로 만들어서 진행

<아래는 위에 쓰여있는 것에 내용 추가>

.has_object_permission(self, request, view, obj)

해당 method를 사용하면 특정 object를 specifically testing 및 checking 한다.

  • ex) 한 리뷰는 해당 리뷰를 작성한 사람만 접근해서 수정할 수 있다. 이때 has_object_permission을 사용할 수 있다.
  • 해당 method는 request가 access 될 때 True를 반환하고 그렇지 않으면 False를 반환한다.
#permissions.py

from rest_framework import permissions

class AdminOrReadOnly(permissions.IsAdminUser):
    def has_permission(self, request, view):
        admin_permission = bool(request.user and request.user.is_staff)
        return request.method == "GET" or admin_permission

permission을 가진 경우 True를 return, permission 없는 경우 False를 return
여기서는 user가 admin이면 True, 아니면 False (AdminOrReadOnly이기 때문에)

# views.py

from watchlist_app.api.permissions import AdminOrReadOnly
...

class ReviewDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset           = Review.objects.all()
    serializer_class   = ReviewSerializer

    permission_classes = [AdminOrReadOnly]

admin 계정으로 들어가면 모든 method를 사용할 수 있지만, 일반 user 계정으로 들어가면 GET method만 사용할 수 있다.

 

.has_permission(self, request, view)

user가 read 또는 다른 것에 대해 permission을 가지고 있는지 일반적으로 checking 한다.

  • 해당 method는 request가 access 될 때 True를 반환하고 그렇지 않으면 False를 반환한다.
  • 해당 request가 read 또는 write와 관련이 있는지 테스트할 필요가 있으면 constant SAFE_METHOD로 체크하는 과정이 필요한데, 이것은 GET, OPTION, HEAD를 담고 있는 튜플이다.
# permissions.py

class ReviewUserOrReadOnly(permissions.BasePermission):
    def has_object_permission(self, request, view, obj):
        if request.method in permissions.SAFE_METHODS:
            return True
        else:
            return obj.review_user == request.user

if 절에서 request.method에 GET 요청이 오면 user가 데이터에 접근하고 있음을 의미. 여기 level에서는 read는 누구든 할 수 있기 때문에 True를 리턴하고 데이터를 보여주면 된다 (GET).

else로 넘어가면 request.method가 GET이 아니라 POST, PUT, DELETE 요청을 보낸다는 것을 의미하는데, 이때는 review를 작성한 user와 요청을 보낸 user가 같은 user인지를 비교를 한다. 
review user이면 모든 요청을 처리할 수 있고, 다른 user이면 GET만 가능하다.

# views.py

...
from watchlist_app.api.permissions import ReviewUserOrReadOnly
...

class ReviewDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset           = Review.objects.all()
    serializer_class   = ReviewSerializer

    permission_classes = [ReviewUserOrReadOnly]