DRF-Permission

jeong_hyeon·2022년 6월 11일
1

Django_DRF

목록 보기
11/12
post-custom-banner

Authentication이 로그인 여부 인증이라면

Permission은 Authorization 즉 로그인한 사용자가 어디까지 서비스를 이용할수있는지에 대한 권한이다.

클래스 속성 또는 데코레이터를 통해 새 권한 클래스를 설정하면 settings.py 파일에 설정된 기본 목록을 무시하도록 뷰에 지시하는 것이다.

1. Setting permission

만약 ,permission을 세팅하지 않는다면 누구나 애플리케이션에 접근 할수있다.

default 상태

'DEFAULT_PERMISSION_CLASSES': [
   'rest_framework.permissions.AllowAny',
]

만약 애플리케이션에 대해 인증된 사용자만 접근하게하려면 settings.py 에 아래의 코드를 추가하면된다.

REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ]
}

하지만 통상 웹서비스에서는 특정뷰 혹은 클래스에서 인증을 필요로 함으로 좀 더 작은 단위로 쪼개서 Permission을 설정해주어야한다.

Authentication

인증의 종류

Django에서 지원하는 인증의 종류는 총 4가지가 있다.

  • Session Authentication

    • 세션을 통한 인증여부 체크
    • APIView를 통해 디폴트 지정 (우선순위 1순위)
  • BasicAuthentication

    • Basic 인증헤더를 통한 인증 수행
    • ex) Authorization : Basic YWxsaWV1czE6MTAyOXNoYWtl
    • APIView를 통해 디폴트 지정 (우선순위 2)
  • Token Authentication

    • Token 헤더를 통한 인증수행
    • ex) Authorization : Token 401f7ac837da42b97f613d789819ff93537bee6a
  • RemoteUserAuthentication

    • User 정보가 다른 서비스 에서 관리 될때 Remote인증
    • Remote-User 헤더를 통한 인증수행

Permission

Django 에서의 권한

django 는 기본적인 권한을 제공해주고있다.

  • is_superuser

    • createsuper로 생성한 user에 대해 True
    • True일 경우 별도 permission없이 모든 권한 적용
  • is_staff

    • True일 경우 admin페이지 접속가능
    • 나머지는 일반유저와 동일
  • is_activae

    • False일경우 모든 권한 불허
    • 로그인도 불가능

DRF에서 기본 제공하는 Permission

  • AllowAny : 인증여부에 상관없이 뷰호출 허용(default)
  • IsAuthenticated : 인증 요청에 한해서 뷰 호출 허용
  • IsAdminUser : Staff인증 요청에 한해서 뷰 호출 허용
  • IsAuthenticatedOrReadOnly : 비인증 요청에게는 읽기 권한만 허용
  • DjangoModelPermission : 인증된 요청에 한해서만 뷰 호출 허용, 추가로 인증 권한 체크를 수행
  • DjangoModelPermissionsOrAnonReadOnly :DjangoModelPermission 과 유사 하나 비인증 요청에 대해서는 읽기 권한만 허용
  • DjangoObjectPermissions : 비인증된 요청 거부, 인증된 레코드 접근에 대한 권한체크를 수행

Custom Permission

DRF에서 제공 해주긴 하지만 커스텀을 통해 자체 제작도 가능하다.

모든 Permission 클래는 2가지 함수를 선택적으로 구현한다.

  • has_permission(request, view)
    • 뷰 호출 접근 권한
    • APIView 접근시 체크
  • has_object_permission(request,view,obj)
    • 개별 레코드 접근 권한

    • APIView 의 get_object함수를 통해 object획득시 체크

    • 브라우저를 통한 API접근시에 CREATE/UPDATE Form 노출여부 확인시

      """
      Provides a set of pluggable permission policies.
      """
      from django.http import Http404
      
      from rest_framework import exceptions
      
      # 안전한 method를 따로 정의 아래의 method는 수정 삭제 삽입을 하지 않아서 안전하다.
      SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
      
      class OperationHolderMixin:
          def __and__(self, other):
              return OperandHolder(AND, self, other)
      
          def __or__(self, other):
              return OperandHolder(OR, self, other)
      
          def __rand__(self, other):
              return OperandHolder(AND, other, self)
      
          def __ror__(self, other):
              return OperandHolder(OR, other, self)
      
          def __invert__(self):
              return SingleOperandHolder(NOT, self)
      
      class SingleOperandHolder(OperationHolderMixin):
          def __init__(self, operator_class, op1_class):
              self.operator_class = operator_class
              self.op1_class = op1_class
      
          def __call__(self, *args, **kwargs):
              op1 = self.op1_class(*args, **kwargs)
              return self.operator_class(op1)
      
      class OperandHolder(OperationHolderMixin):
          def __init__(self, operator_class, op1_class, op2_class):
              self.operator_class = operator_class
              self.op1_class = op1_class
              self.op2_class = op2_class
      
          def __call__(self, *args, **kwargs):
              op1 = self.op1_class(*args, **kwargs)
              op2 = self.op2_class(*args, **kwargs)
              return self.operator_class(op1, op2)
      
      class AND:
          def __init__(self, op1, op2):
              self.op1 = op1
              self.op2 = op2
      
          def has_permission(self, request, view):
              return (
                  self.op1.has_permission(request, view) and
                  self.op2.has_permission(request, view)
              )
      
          def has_object_permission(self, request, view, obj):
              return (
                  self.op1.has_object_permission(request, view, obj) and
                  self.op2.has_object_permission(request, view, obj)
              )
      
      class OR:
          def __init__(self, op1, op2):
              self.op1 = op1
              self.op2 = op2
      
          def has_permission(self, request, view):
              return (
                  self.op1.has_permission(request, view) or
                  self.op2.has_permission(request, view)
              )
      
          def has_object_permission(self, request, view, obj):
              return (
                  self.op1.has_object_permission(request, view, obj) or
                  self.op2.has_object_permission(request, view, obj)
              )
      
      class NOT:
          def __init__(self, op1):
              self.op1 = op1
      
          def has_permission(self, request, view):
              return not self.op1.has_permission(request, view)
      
          def has_object_permission(self, request, view, obj):
              return not self.op1.has_object_permission(request, view, obj)
      
      class BasePermissionMetaclass(OperationHolderMixin, type):
          pass
      
      class BasePermission(metaclass=BasePermissionMetaclass):
          """
          A base class from which all permission classes should inherit.
          """
      
          def has_permission(self, request, view):
              """
              Return `True` if permission is granted, `False` otherwise.
              """
              return True
      
          def has_object_permission(self, request, view, obj):
              """
              Return `True` if permission is granted, `False` otherwise.
              """
              return True
      
      # 모든요청에 대해서 허가
      class AllowAny(BasePermission):
          """
          Allow any access.
          This isn't strictly required, since you could use an empty
          permission_classes list, but it's useful because it makes the intention
          more explicit.
          """
      
          def has_permission(self, request, view):
              return True
      
      #유저가 존재하고 로그인 되어 있을 경우에 허가합니다.
      class IsAuthenticated(BasePermission):
          """
          Allows access only to authenticated users.
          """
      
          def has_permission(self, request, view):
              return bool(request.user and request.user.is_authenticated)
      
      #**유저가 존재하고 스태프일 경우에 허가합니다.**
      class IsAdminUser(BasePermission):
          """
          Allows access only to admin users.
          """
      
          def has_permission(self, request, view):
              return bool(request.user and request.user.is_staff)
      
      #안전한 request method 이거나 유저가 존재하고 로그인 되어 있을 경우에 허가합니다.
      class IsAuthenticatedOrReadOnly(BasePermission):
          """
          The request is authenticated as a user, or is a read-only request.
          """
      
          def has_permission(self, request, view):
              return bool(
                  request.method in SAFE_METHODS or
                  request.user and
                  request.user.is_authenticated
              )
      
      class DjangoModelPermissions(BasePermission):
          """
          The request is authenticated using `django.contrib.auth` permissions.
          See: https://docs.djangoproject.com/en/dev/topics/auth/#permissions
      
          It ensures that the user is authenticated, and has the appropriate
          `add`/`change`/`delete` permissions on the model.
      
          This permission can only be applied against view classes that
          provide a `.queryset` attribute.
          """
      
          # Map methods into required permission codes.
          # Override this if you need to also provide 'view' permissions,
          # or if you want to provide custom permission codes.
          perms_map = {
              'GET': [],
              'OPTIONS': [],
              'HEAD': [],
              'POST': ['%(app_label)s.add_%(model_name)s'],
              'PUT': ['%(app_label)s.change_%(model_name)s'],
              'PATCH': ['%(app_label)s.change_%(model_name)s'],
              'DELETE': ['%(app_label)s.delete_%(model_name)s'],
          }
      
          authenticated_users_only = True
      
          def get_required_permissions(self, method, model_cls):
              """
              Given a model and an HTTP method, return the list of permission
              codes that the user is required to have.
              """
              kwargs = {
                  'app_label': model_cls._meta.app_label,
                  'model_name': model_cls._meta.model_name
              }
      
              if method not in self.perms_map:
                  raise exceptions.MethodNotAllowed(method)
      
              return [perm % kwargs for perm in self.perms_map[method]]
      
          def _queryset(self, view):
              assert hasattr(view, 'get_queryset') \
                  or getattr(view, 'queryset', None) is not None, (
                  'Cannot apply {} on a view that does not set '
                  '`.queryset` or have a `.get_queryset()` method.'
              ).format(self.__class__.__name__)
      
              if hasattr(view, 'get_queryset'):
                  queryset = view.get_queryset()
                  assert queryset is not None, (
                      '{}.get_queryset() returned None'.format(view.__class__.__name__)
                  )
                  return queryset
              return view.queryset
      
          def has_permission(self, request, view):
              # Workaround to ensure DjangoModelPermissions are not applied
              # to the root view when using DefaultRouter.
              if getattr(view, '_ignore_model_permissions', False):
                  return True
      
              if not request.user or (
                 not request.user.is_authenticated and self.authenticated_users_only):
                  return False
      
              queryset = self._queryset(view)
              perms = self.get_required_permissions(request.method, queryset.model)
      
              return request.user.has_perms(perms)
      
      class DjangoModelPermissionsOrAnonReadOnly(DjangoModelPermissions):
          """
          Similar to DjangoModelPermissions, except that anonymous users are
          allowed read-only access.
          """
          authenticated_users_only = False
      
      class DjangoObjectPermissions(DjangoModelPermissions):
          """
          The request is authenticated using Django's object-level permissions.
          It requires an object-permissions-enabled backend, such as Django Guardian.
      
          It ensures that the user is authenticated, and has the appropriate
          `add`/`change`/`delete` permissions on the object using .has_perms.
      
          This permission can only be applied against view classes that
          provide a `.queryset` attribute.
          """
          perms_map = {
              'GET': [],
              'OPTIONS': [],
              'HEAD': [],
              'POST': ['%(app_label)s.add_%(model_name)s'],
              'PUT': ['%(app_label)s.change_%(model_name)s'],
              'PATCH': ['%(app_label)s.change_%(model_name)s'],
              'DELETE': ['%(app_label)s.delete_%(model_name)s'],
          }
      
          def get_required_object_permissions(self, method, model_cls):
              kwargs = {
                  'app_label': model_cls._meta.app_label,
                  'model_name': model_cls._meta.model_name
              }
      
              if method not in self.perms_map:
                  raise exceptions.MethodNotAllowed(method)
      
              return [perm % kwargs for perm in self.perms_map[method]]
      
          def has_object_permission(self, request, view, obj):
              # authentication checks have already executed via has_permission
              queryset = self._queryset(view)
              model_cls = queryset.model
              user = request.user
      
              perms = self.get_required_object_permissions(request.method, model_cls)
      
              if not user.has_perms(perms, obj):
                  # If the user does not have permissions we need to determine if
                  # they have read permissions to see 403, or not, and simply see
                  # a 404 response.
      
                  if request.method in SAFE_METHODS:
                      # Read permissions already checked and failed, no need
                      # to make another lookup.
                      raise Http404
      
                  read_perms = self.get_required_object_permissions('GET', model_cls)
                  if not user.has_perms(read_perms, obj):
                      raise Http404
      
                  # Has read permissions.
                  return False
      
              return True

      참조

      https://github.com/encode/django-rest-framework/blob/master/rest_framework/permissions.py

post-custom-banner

0개의 댓글