diadia

興味があることをやってみる。自分のメモを残しておきます。

APIViewのauthentication_classesとpermission_classesについて分かったところまで。。。

書きかけ。。。

APIViewのオリジナルコード

django-rest-framework/views.py at master · encode/django-rest-framework · GitHub

# APIViewのプロパティ  

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 #コイツ
    content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
    metadata_class = api_settings.DEFAULT_METADATA_CLASS
    versioning_class = api_settings.DEFAULT_VERSIONING_CLASS

    # Allow dependency injection of other settings to make testing easier.
    settings = api_settings

    schema = DefaultSchema()

APIViewの継承時にauthentication_classesを設定すると、その認証が必要になり、またpermission_classesを設定するとその権限を持っているものしかAPIViewを起動することができなくなる。これはとてもわかりやすく使いやすいと思っていたが具体的にどんな仕組みで動いているのか理解できていなかったので少し調べてみる。

APIView継承のコード例




authentication_classesはどのように使われているか?

authentication_classesが使われているメソッドは何か?

# https://github.com/encode/django-rest-framework/blob/master/rest_framework/views.py#L268

    def get_authenticators(self):
        """
        Instantiates and returns the list of authenticators that this view can use.
        """
        return [auth() for auth in self.authentication_classes]

これのみである。このメソッドでautehtication_classのインスタンスのリストを作成する。

つぎにこのリストはどこで使われているかが次の疑問である。てことでこのメソッドがどこで使われているか見ると以下の2つのメソッドが見つかった。

  1. get_authenticate_header
  2. initialize_request
# https://github.com/encode/django-rest-framework/blob/master/rest_framework/views.py#L183

    def get_authenticate_header(self, request):
        """
        If a request is unauthenticated, determine the WWW-Authenticate
        header to use for 401 responses, if any.
        """
        authenticators = self.get_authenticators()
        if authenticators:
            return authenticators[0].authenticate_header(request)

適切な認証情報が与えられずに失敗した場合にどのような認証タイプを提供するかリクエストヘッダに提供するために使うメソッドだと考えられる。

# https://github.com/encode/django-rest-framework/blob/master/rest_framework/views.py#L385

    def initialize_request(self, request, *args, **kwargs):
        """
        Returns the initial request object.
        """
        parser_context = self.get_parser_context(request)

        return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )

このメソッドで使われているのは分かったけど、このメソッドはどこで使われているか??? それはdisatchメソッドである。

# https://github.com/encode/django-rest-framework/blob/master/rest_framework/views.py#L485

    def dispatch(self, request, *args, **kwargs):
        """
        `.dispatch()` is pretty much the same as Django's regular dispatch,
        but with extra hooks for startup, finalize, and exception handling.
        """
        self.args = args
        self.kwargs = kwargs
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?

        try:
            self.initial(request, *args, **kwargs)

            # Get the appropriate handler method
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed

            response = handler(request, *args, **kwargs)

このメソッドはdjangoのオリジナルのViewにas_viewメソッドが存在する。これによってdispatchメソッドが呼び出される。

このdispatchメソッド内のinitialize_requestメソッドが呼ばれ、requestオブジェクトが初期化される。またその後、initialメソッドがdispatchメソッド内で呼ばれる。
これが何なのか見てみると認証につながっていることが分かる。

# https://github.com/encode/django-rest-framework/blob/3db88778893579e1d7609b584ef35409c8aa5a22/rest_framework/views.py#L399


    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)

perform_authenticationメソッドにつながっている。
perform_authenticationメソッドでは、request.userを実行しているだけである。しかしここが肝でrequest.userのuserは@propertyがついているものでその内部は、認証を行っている。てことで、perform_authenticationとRequest,userメソッドを参照する。

# https://github.com/encode/django-rest-framework/blob/3db88778893579e1d7609b584ef35409c8aa5a22/rest_framework/views.py#L316

    def perform_authentication(self, request):
        """
        Perform authentication on the incoming request.
        Note that if you override this and simply 'pass', then authentication
        will instead be performed lazily, the first time either
        `request.user` or `request.auth` is accessed.
        """
        request.user
# https://github.com/encode/django-rest-framework/blob/3db88778893579e1d7609b584ef35409c8aa5a22/rest_framework/request.py#L140


class Request:
    """
    Wrapper allowing to enhance a standard `HttpRequest` instance.
    Kwargs:
        - request(HttpRequest). The original request instance.
        - parsers(list/tuple). The parsers to use for parsing the
          request content.
        - authenticators(list/tuple). The authenticators used to try
          authenticating the request's user.
    """

    def __init__(self, request, parsers=None, authenticators=None,
                 negotiator=None, parser_context=None):
        assert isinstance(request, HttpRequest), (
            'The `request` argument must be an instance of '
            '`django.http.HttpRequest`, not `{}.{}`.'
            .format(request.__class__.__module__, request.__class__.__name__)
        )

        self._request = request
        self.parsers = parsers or ()
        self.authenticators = authenticators or ()
        self.negotiator = negotiator or self._default_negotiator()
        self.parser_context = parser_context
        self._data = Empty
        self._files = Empty
        self._full_data = Empty
        self._content_type = Empty
        self._stream = Empty

        if self.parser_context is None:
            self.parser_context = {}
        self.parser_context['request'] = self
        self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET

        force_user = getattr(request, '_force_auth_user', None)
        force_token = getattr(request, '_force_auth_token', None)
        if force_user is not None or force_token is not None:
            forced_auth = ForcedAuthentication(force_user, force_token)
            self.authenticators = (forced_auth,)

 ...省略

    @property
    def user(self):
        """
        Returns the user associated with the current request, as authenticated
        by the authentication classes provided to the request.
        """
        if not hasattr(self, '_user'):
            with wrap_attributeerrors():
                self._authenticate()
        return self._user
 
...省略

    def _authenticate(self):
        """
        Attempt to authenticate the request using each authentication instance
        in turn.
        """
        for authenticator in self.authenticators:
            try:
                user_auth_tuple = authenticator.authenticate(self)
            except exceptions.APIException:
                self._not_authenticated()
                raise

            if user_auth_tuple is not None:
                self._authenticator = authenticator
                self.user, self.auth = user_auth_tuple
                return

        self._not_authenticated()

user_auth_tupleは各authenticateクラスのauthenticateメソッドの戻り値である。 Basic, Session, Tokenでの認証が存在していて、おのおの戻り値は以下のようになっている。

認証クラス 認証クラスのauthenticateメソッドの戻り値(=user_auth_tuple) 該当コード
BasicAuthentication (user, None) https://github.com/encode/django-rest-framework/blob/3db88778893579e1d7609b584ef35409c8aa5a22/rest_framework/authentication.py#L53
SessionAuthentication (user, None) https://github.com/encode/django-rest-framework/blob/3db88778893579e1d7609b584ef35409c8aa5a22/rest_framework/authentication.py#L112
TokenAuthentication (token.user, token) https://github.com/encode/django-rest-framework/blob/3db88778893579e1d7609b584ef35409c8aa5a22/rest_framework/authentication.py#L151

そういうわけで認証が成功するとuserオブジェクトがrequestオブジェクトに格納される流れとなる。

permissonについては以下で。
https://torajirousan.hatenadiary.jp/entry/2021/01/03/223928