2021年7月24日土曜日

【和訳】Django Rest Framework チュートリアル4:認証とアクセス許可

【和訳】Django Rest Framework クイックスタート

【和訳】Django Rest Framework チュートリアル1:シリアル化

【和訳】Django Rest Framework チュートリアル2:リクエストとレスポンス

【和訳】Django Rest Framework チュートリアル3:クラスベースのビュー

【和訳】Django Rest Framework チュートリアル4:認証とアクセス許可

【和訳】Django Rest Framework チュートリアル5:リレーションとハイパーリンクされたAPI

【和訳】Django Rest Framework チュートリアル6:ビューセットとルーター 


チュートリアル4:認証とアクセス許可

現在、APIには、コードスニペットを編集または削除できるユーザーに関して制限はありません。
以下の制限を設けるために、より高度な動作が必要です。


  • コードスニペットは常に作成者に関連付けられている。
  • 認証されたユーザーのみがスニペットを作成できる。
  • スニペットの作成者のみが更新または削除できる。
  • 未認証のリクエストに対しては、読み取り専用(Read-only)のアクセス権のみ許可する。


モデルに情報を追加する

Snippetモデルクラスにいくつかの変更を加えます
まず、フィールドを2つ追加しましょう。
追加したフィールドのうち、1つはコードスニペットを作成したユーザーを表すために使用されます。
もう1つのフィールドは、強調表示されたコードのHTML表示を格納するために使用されます。

次の2つのフィールドをmodels.pySnippetモデルに追加します。

owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()

また、モデルを保存するときに、pygmentsコード強調表示ライブラリを使用して、highlightedフィールドにデータを入力する必要があります。

そのため、追加でモジュールをインポートする必要があります。

from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight

これで、モデルクラスに.save()メソッドを追加できます。

def save(self, *args, **kwargs):
    """
    コードスニペットのハイライトされたHTML表示を作成するために、
    `pygments`ライブラリを使用する
    """
    lexer = get_lexer_by_name(self.language)
    linenos = 'table' if self.linenos else False
    options = {'title': self.title} if self.title else {}
    formatter = HtmlFormatter(style=self.style, linenos=linenos,
                              full=True, **options)
    self.highlighted = highlight(self.code, lexer, formatter)
    super(Snippet, self).save(*args, **kwargs)

この作業がすべて完了したら、データベーステーブルを更新する必要があります。
通常、これを行うためにデータベースmigrateを作成しますが、このチュートリアルの目的のために、データベースを削除してやり直してみましょう。

rm -f db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate

APIのテストに使用するために、異なるユーザーを数名作成する方がいいかもしれません。
これを行う最も簡単な方法は、
createsuperuserコマンドを使用することです。

python manage.py createsuperuser


ユーザーモデルのエンドポイントの追加

ユーザーを数名操作できるようになったので、APIがそれらのユーザーを表示できるよう、表示機能を追加します。

新しいシリアライザーの作成は簡単です。serializers.pyに以下を追加します:

from django.contrib.auth.models import User

class UserSerializer(serializers.ModelSerializer):
    snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())

    class Meta:
        model = User
        fields = ['id', 'username', 'snippets']

'snippets'Userモデル上では逆参照の関係にあるので、ModelSerializerクラスの使用時には、デフォルトではインクルードされません。そのため、私たちはそれを明示的にフィールドを追加する必要があります。

また、views.pyにいくつかのビューを追加します。
ユーザー表示には、読み取り専用ビューのみを使用したいのでジェネリッククラスベースのビューであるListAPIViewRetrieveAPIView使用します。

from django.contrib.auth.models import User


class UserList(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer


class UserDetail(generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

UserSerializerクラスもインポートしてください

from snippets.serializers import UserSerializer

最後に、URL confからそれらを反映し、これらのビューをAPIに追加する必要があります。
以下を
snippets/urls.pyのパターンに追加します。

path('users/', views.UserList.as_view()),
path('users/<int:pk>/', views.UserDetail.as_view()),


スニペットとユーザーの関連付け

現在、コードスニペットを作成した場合、スニペットを作成したユーザーをスニペットインスタンスに関連付ける方法はありません。
ユーザーはシリアル化された表現の一部として送信されるのではなく、送られてくるリクエストのプロパティとして送信されてきます。

これに対処する方法として、スニペットビューの.perform_create()メソッドをオーバーライドするやり方があります。これにより、インスタンスの保存の管理方法を変更し、受信リクエストまたはリクエストされたURLに暗黙的に含まれる情報を処理できます。

上のSnippetListビュークラスに、次のメソッドを追加します。

def perform_create(self, serializer):
    serializer.save(owner=self.request.user)

create()シリアライザーメソッドにはリクエストからの検証済みデータとともに、追加の'owner'フィールドが渡されます。


シリアライザーの更新

スニペットと作成ユーザーが関連付けられたので、それを反映するようにSnippetSerializer更新してみましょう
次のフィールドを
serializers.py中のシリアライザーの定義に追加します

owner = serializers.ReadOnlyField(source='owner.username')

:内側のMetaクラスのフィールドのリストにも'owner'を必ず追加してください

このフィールドは非常に興味深いことをしています。
source引数は、フィールドを埋めるために、参照先テーブルのどの属性を使用するかをコントロールし、また、シリアル化されたインスタンスのどの属性でも選択できます。
Djangoのテンプレート言語で使用されるのと同様に、
上記のようにドット表記を使用することで、source引数は、指定された属性を辿ることができます'owner.username')。

私たちが追加したフィールドは、型のないReadOnlyFieldであり、CharFieldBooleanFieldのような他の型のフィールドとは対照的です。
ReadOnlyFieldは常に読み取り専用であり、シリアライズされた表現のために使用されますが、インスタンスの逆シリアル化された時に、モデルインスタンスを更新するために使用されることはありません。

なお、ここでは、CharField(read_only=True)を使用することもできます。


ビューに必要な権限を追加する

コードスニペットとユーザーが関連付けられたので、認証されたユーザーのみがコードスニペットを作成、更新、および削除できるようにします。

RESTフレームワークには、特定のビューにアクセスできるユーザーを制限するために使用できるいくつかのアクセス許可クラスが含まれています。
今回、私たちが探し求めているの
IsAuthenticatedOrReadOnlyです。
このクラスにより、認証されたリクエストは読み取り/書き込みアクセスを取得し、認証されていないリクエストは読み取り専用アクセスを取得します。

まず、ビューモジュールに次のインポートを追加します

from rest_framework import permissions

その後、SnippetListSnippetDetailビュークラスの両方に次のプロパティを追加します

permission_classes = [permissions.IsAuthenticatedOrReadOnly]


ブラウザから閲覧可能なAPIへのログインの追加

現時点でブラウザを開いてブラウザから閲覧可能なAPIに移動すると、新しいコードスニペットを作成できなくなっていることがわかります。
新しいコードスニペットを作成するためには、ユーザーとしてログインできるようにしなければなりません。

プロジェクトレベルのurls.pyファイルでURLconfを編集することにより、ブラウザから閲覧可能なAPIで使用するログインビューを追加できます。

ファイルの先頭で次のライブラリをインポートします。

from django.urls import path, include

また、ファイルの最後に、閲覧可能なAPIのログインビューとログアウトビューを含めるパターンを追加します。

urlpatterns += [
    path('api-auth/', include('rest_framework.urls')),
]

'api-auth/'パターン一部は、実際には使用したいURLにすることができます。

ここで、ブラウザをもう一度開いてページを更新すると、ページの右上に「ログイン」リンクが表示されます。以前に作成したユーザーの1人としてログインすると、コードスニペットを再度作成できるようになります。

いくつかのコードスニペットを作成したら、「/ users /」エンドポイントに移動します。
表現には、各ユーザーの「スニペット」フィールドに、各ユーザーに関連付けられているスニペットIDのリストが含まれていることに注意してください。


オブジェクトレベルの権限

実際には、すべてのコードスニペットを誰にでも表示できるようにしたいだけでなく、コードスニペットを作成したユーザーだけがコードスニペットを更新または削除できるようにします

そのためには、カスタム権限を作成する必要があります。

スニペットアプリで、新しいファイルを作成し、 permissions.py

from rest_framework import permissions


class IsOwnerOrReadOnly(permissions.BasePermission):
    """
    オブジェクトの編集を、オーナーのみに許可するカスタムパーミッション
    """

    def has_object_permission(self, request, view, obj):
        # 読み取り権限はあらゆるリクエストに許可される。
        # そのため、常にGET、HEAD、OPTIONSリクエストを許可する
        if request.method in permissions.SAFE_METHODS:
            return True

        # 書き込み権限はスニペットのオーナーのみに許可される。
        return obj.owner == request.user

SnippetDetailビュークラスのpermission_classesプロパティを編集して、このカスタム権限をスニペットインスタンスのエンドポイントに追加できます

permission_classes = [permissions.IsAuthenticatedOrReadOnly,
                      IsOwnerOrReadOnly]

IsOwnerOrReadOnlyクラスもインポートしてください

from snippets.permissions import IsOwnerOrReadOnly

ここで、ブラウザを再度開くと、コードスニペットを作成したのと同じユーザーとしてログインしている場合にのみ、「DELETE」アクションと「PUT」アクションがスニペットインスタンスエンドポイントに表示されることがわかります。


APIを使用した認証

APIに対して一連の権限を設けたので、スニペットを編集する場合は、APIへのリクエストを認証する必要があります
私たちはこれまでのところ、
認証クラスを何も設定していないので、デフォルトのSessionAuthenticationBasicAuthenticationが現在適用されている。

Webブラウザーを介してAPIと対話すると、ログインでき、ブラウザーセッションがリクエストに必要な認証を提供します。

プログラムでAPIを操作している場合は、リクエストごとに認証資格を明示的に提供する必要があります。

認証せずにスニペットを作成しようとすると、エラーが発生します。

http POST http://127.0.0.1:8000/snippets/ code="print(123)"

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

以前に作成したユーザーの1人のユーザー名とパスワードを含めることで、リクエストを成功させることができます。

http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print(789)"

{
    "id": 1,
    "owner": "admin",
    "title": "foo",
    "code": "print(789)",
    "linenos": false,
    "language": "python",
    "style": "friendly"
}
※訳注:毎回IDとパスワードを送信する代わりに、jwt(JSON Web Token)を用いてログイン処理を行える。

概要

これで、Web APIに対するかなりきめ細かい権限のセットと、システムのユーザーおよびユーザーが作成したコードスニペットのエンドポイントが得られました。

チュートリアルのパート5では、私たちは強調表示スニペットのためのHTMLエンドポイントを作成することにより、すべてをひとまとめにする方法、および、システム内の関係にハイパーリンクを使用することで、私たちのAPIの凝集度を向上させる方法を見ていきます。

0 件のコメント:

コメントを投稿

【和訳】Django Rest Framework 目次

目次 【和訳】Django Rest Framework クイックスタート 【和訳】Django Rest Framework チュートリアル1:シリアル化 【和訳】Django Rest Framework チュートリアル2:リクエストとレスポンス 【和訳】Django Res...