diadia

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

django permissonでリソースを制御する

webアプリの場合、認証する->認可するの流れをたどる。 この認可されたパーミッションにしたがってリソースのアクセス制御を実施したい。

djangoの場合どうすれば実装できるか?

下準備

  1. Userオブジェクトにパーミッションを付加する
  2. Groupオブジェクトにパーミッションを付加する
  3. パーミッションを全く持たないUserオブジェクトをGroupオブジェクトに追加する

結果から言うと1のUserオブジェクトもアクセスでき、3のUserオブジェクトもアクセスできる。そしてパーミッションを全く付加していないsuperuserもリソースにアクセスできた。当然、パーミッションを追加していない普通のUserオブジェクトはリソースにアクセスできないのは言うまでもない。

下準備に関してはdjangoのあどみんからパーミッションを付与するか、コードベースで付与するか考えられる。コードベースでやる場合は以下を参考にすると良い.

Djangoのpermissionを付与するサンプルコード - diadia

実装する

# models.py リソース制御対象のリソースモデル

from django.db import models
from django.contrib.auth.models import User
# Create your models here.

class Articulo(models.Model):
    title = models.CharField(max_length=255)
    content = models.TextField()
    created_by = models.ForeignKey(User, on_delete=models.PROTECT)
    created_at = models.DateTimeField()

    def __str__(self):
        return self.title
# views.py
from django.shortcuts import render
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.views.generic import View
from .models import Articulo

class ArticulosListView(PermissionRequiredMixin, View):

    permission_required = ('articulos.view_articulo')

    def get(self, request):
        articulos_objects = Articulo.objects.all()
        context = {"articulos_objects": articulos_objects}
        return render(request, 'articulos/list.html', context)

これでリソース制御できる。 permission_requiredはPermissionRequiredMixinのプロパティである。値はstr型でも何らかのシーケンスで定める。

値はapp_label.view_小文字のモデルで定める。これがいわゆるリソースに対するパーミッションである。

https://docs.djangoproject.com/ja/3.0/topics/auth/default/#default-permissions

リソース操作 例 app_labelがhogesでHogeモデルの場合
読み取り hoges.view_hoge
作成 hogs.add_hoge
変更編集 hoges.change_hoge
削除 hoges.delete_hoge

djangoのコードがどうなっているか

https://github.com/django/django/blob/master/django/contrib/auth/mixins.py#L74

class PermissionRequiredMixin(AccessMixin):
    """Verify that the current user has all specified permissions."""
    permission_required = None

    def get_permission_required(self):
        """
        Override this method to override the permission_required attribute.
        Must return an iterable.
        """
        if self.permission_required is None:
            raise ImproperlyConfigured(
                '{0} is missing the permission_required attribute. Define {0}.permission_required, or override '
                '{0}.get_permission_required().'.format(self.__class__.__name__)
            )
        if isinstance(self.permission_required, str):
            perms = (self.permission_required,)
        else:
            perms = self.permission_required
        return perms

    def has_permission(self):
        """
        Override this method to customize the way permissions are checked.
        """
        perms = self.get_permission_required()
        return self.request.user.has_perms(perms)

    def dispatch(self, request, *args, **kwargs):
        if not self.has_permission():
            return self.handle_no_permission()
        return super().dispatch(request, *args, **kwargs)

classmethodのas_viewがdispatchを呼び出す事になっているが、このdispatchを呼び出す前にプロパティのパーミッションを取得(get_permission_required)して、 Userオブジェクトのメソッドであるhas_permでパーミッションがあるかどうかをチェックする(has_permission)。このフラグをmixinのdispatch内で判定し、Trueであれば、各Viewのget, postメソッドにつないでいく仕組みになる。

まだ、どうやってas_viewとdispatchの間にこのロジックを差し込む原因があるのか調べきれていない。おそらくas_view()になんか書いてあるのか???

参考資料

https://thinkami.hatenablog.com/entry/2016/02/03/062159