Django Rest framework Permissions


今日は久しぶりにDjango Postingをしました!インフラ面の問題や社内業務の終了、文書化に関わることが多く、特徴開発ができず、今になってやっと特徴開発を開始しました.
今日議論する内容はDjango Rest FrameworkのPermission機能です.
私が新しく実現した機能はLMS組織とクラス管理システムです.組織およびクラス管理システムの場合、特定のAPIを呼び出すには、特定の「権限」が必要です.「権限」は、次のようにモデリングされています.

特定のユーザーは、「聖約」、「組織」または「EasyClass」と呼ばれるモデルにロールを持ちます.ロールです.ロールは、複数の特権、すなわち権限で構成されます.
次に、特定のAPI内のどのユーザが特定の権限を持っているかを確認するためにコードを作成しようとします.既存の旧式コードもそうですが、簡単に考えれば構築できるコードは以下の通りです.
class CreateView(generics.ListCreateAPIView):
	def perform_create(self, serializer):
	    if self.request.user.has_some_permission():
	        return serializer.save(user=self.request.user)
	
	    raise PermissionDenied()
しかし、このようにコードを記述すると、チェック権限のすべてのビューにブランチ文が含まれ、チェック権限の論理はuserというモデルのドメイン論理に含まれます.もちろん、これも良い方法かもしれませんが、ユーザーというドメインは通常多くのことをしますが、システムに存在する様々な権限に対して、論理を使うのはよくありません.
この場合、Django Rest Frameworkは権限機能の導入を試みることができる.Django権限について説明します.

公式ファイルに基づいてpermissionsは、認証またはアクセス拒否のリクエストを決定します.そして常にviewの起点で検証します.
Rest Frameworkでpermissionsを使用するには、APIViewを基準にpermission classesで必要な権限を指定するだけです.
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

class ExampleView(APIView):
    permission_classes = [IsAuthenticated]

    def get(self, request, format=None):
        content = {
            'status': 'request was permitted'
        }
        return Response(content)
では、APIViewは、指定したpermission classesの論理をどのように処理するかを表示します.
class APIView(View):

    # The following policies may be set at either globally, or per-view.
    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
    parser_classes = api_settings.DEFAULT_PARSER_CLASSES
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES

	...

	def get_permissions(self):
        """
        Instantiates and returns the list of permissions that this view requires.
        """
        return [permission() for permission in self.permission_classes]

	...
	def check_permissions(self, request):
        """
        Check if the request should be permitted.
        Raises an appropriate exception if the request is not permitted.
        """
        for permission in self.get_permissions():
            if not permission.has_permission(request, self):
                self.permission_denied(
                    request,
                    message=getattr(permission, "message", None),
                    code=getattr(permission, "code", None),

	...
	def initial(self, request, *args, **kwargs):
        """
        Runs anything that needs to occur prior to calling the method handler.
        """
        self.format_kwarg = self.get_format_suffix(**kwargs)

        # Perform content negotiation and store the accepted info on the request
        neg = self.perform_content_negotiation(request)
        request.accepted_renderer, request.accepted_media_type = neg

        # Determine the API version, if versioning is in use.
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme

        # Ensure that the incoming request is permitted
        self.perform_authentication(request)
        self.check_permissions(request)
        self.check_throttles(request)
APIViewのデフォルトにはpermission classes変数があり、デフォルト値はDjango設定で設定した値です.検証プロセスは、Default値を使用してAPI全体に適用できます.(good)
check permissionsロジックから見ると、has permissionはすべての権限に対して呼び出され、ある権限が満たされない場合、permission deferred処理が行われる.このときhas permissionは簡単にFalse,Trueを返す.
さらにcheck permissionsロジックはmethod handlerを呼び出す前に呼び出される.
すなわち、APIViewを継承したインプリメンテーションでpermission classes配列で使用するクラスのみが指定されている場合、残りのクラスは親クラスにPermissionクラスオブジェクトを作成し、クラスのhas permission()関数で権限をチェックします.
以下は基本的なBasePermissionクラスです.
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
上記のBasePermissionを継承して実装できる権限をカスタマイズします.
今初めて帰って、私は権限管理モデルを設計して、モデルの結果に基づいてどのユーザーがどのテナント、組織、クラスなどに対して特定の権限を持っているかを検証しました.has permission,has object permissionではhas object permissionが実現されている.権限を検証するためには、リクエスト中のuserのほかに他のモデルが必要であるためである.
class OrganizationEditPermission:
    def has_object_permission(self, request, view, obj: Tenant):
        _ = view

        user = request.user
        if not bool(user and user.is_authenticated):
            return False

        if not (
            Privilege.objects.filter(name=Privilege.Type.ORGANIZATION_EDIT)
            .filter(role__user=user)
            .filter(role__roleowner__tenant=obj)
            .exists()
        ):
            **return False

        return True**
実装されたクラスは、Viewで次のように使用されます.
class OrganizationView(generics.CreateAPIView):

    serializer_class = OrganizationCreateSerializer
    permission_classes = [OrganizationEditPermission]

    @transaction.atomic
    def perform_create(self, serializer):
        data = serializer.data
				# if permission denied raise Error.
        self.check_object_permissions(self.request, data["tenant"])
初めてコードを作成する場合、if文で権限をチェックするのは見たくないので、permission classesでCustom Permissionを定義するだけで余分なコードは必要ないと思いますが、テナントや組織などのユーザーのほかに、他のモデルの参照も必要なので、check object permissionsで明示的に権限チェックを行わないわけにはいきません.
しかし、このDjango Restフレームワークで提供されているPermissionを利用して開発することで、Permission関連のコードを集約し、ソフトウェアの凝集力を向上させることができると思います.特に私が構成しているLMSシステムには、様々な権限が存在し、これらの権限検証の論理は一つの場所に集中することができ、permission classesを通じてこのAPIを呼び出すために必要な権限は何なのか、一目瞭然です.
そこで、今回LMSシステムの権限検証ロジックを開発し、Django Rest Frameworkが提供するPermission機能を積極的に利用する予定です.
他の開発者も私に似たような要求やDjango RestFrameworkで「権限」に関連するものを管理する必要がある場合は、権限機能の使用をお勧めします.