Ahogrammer

Deep Dive Into NLP, ML and Cloud

Djangoにおけるアクセス制御の話

Djangoでアプリケーションを作っているとアクセス制御をしたくなることがあります。たとえば、会員サイトではプレミアムユーザと一般ユーザによってアクセスできる情報に差を付けたいことがあるでしょう。こういった機能は、少し規模の大きなサイトではよく見かけます。

Djangoでアクセス制御をする方法はいくつもあります。その中にはDjango組み込みの方法やアプリケーションとして提供されている方法もあります。本記事ではそれらを比較してどのように使えるのか、どういった場面に向いてそうなのかといったことを確認します。

本記事の構成は以下の通りです。

Django組み込みのパーミッション

Django組み込みのパーミッションは、ユーザかグループに適用できるモデルレベルのパーミッションです。INSTALLED_APPSdjango.contrib.authがある場合、Djangomigrateを実行したタイミングで、自動的にすべてのモデルに対して、閲覧(view)、追加(add)、変更(change)、削除(delete)の権限を作成します。この権限をユーザやグループに付与することで、実行できる操作を制限することができます。

組み込みのパーミッションについて例を見てみましょう。たとえば、appという名前のアプリケーションの中にPostという名前のモデルがあるとします。そうすると、migrateを実行した段階で以下の4つのパーミッションが作成されます。

  • app.add_post
  • app.change_post
  • app.delete_post
  • app.view_post

ユーザがこれらの権限を所持しているか確認するために、Userモデルで提供されているhas_permメソッドを使うことができます。

>>> user.has_perm('app.add_post')
True

権限の付与と削除は管理画面の「User permissions」から行うことができます。

f:id:Hironsan:20190419135830p:plain
Permissionの追加と削除

テンプレートから権限を参照したい場合、コンテキストに自動的に追加されるperms変数を使用することができます。

{% if perms.app.view_post %}
    This content will be shown users with view_post permission.
{% endif %}

ここまでは自動的に追加される権限について見てきましたが、自分でカスタムパーミッションを定義することもできます。カスタムパーミッションを定義することで、モデルに対する独自の権限を作成することができます。そのためには、モデルのpermissions属性を使います。

class Post(models.Model):                                                                                                                                
    content = models.TextField()                                                                                                                         
    author = models.ForeignKey(User, on_delete=models.CASCADE)                                                                                           
                                                                                                                                                         
    class Meta:                                                                                                                                          
        permissions = (                                                                                                                                  
            ('view_content', 'View content'),                                                                                                            
        )   

こうすることで、migrateを実行したときに自動的に権限が作成されます。

f:id:Hironsan:20190419140113p:plain
カスタムパーミッションの定義

より詳細な話はDjangoの公式ドキュメントを確認してください。

docs.djangoproject.com

django-guardian

django-guardianは、オブジェクトレベルのパーミッションを実装できるアプリです。Django組み込みのパーミッションがモデル内のすべてのオブジェクトへのアクセスを制御するのに対し、django-guardianはモデルの特定のインスタンスへのアクセスを制御できます。

わかりやすく説明するために具体例を考えましょう。たとえば、ユーザAがPost100を作成したとします。一方、ユーザBはPost200を作成したとします。こういった場合、django-guardianを使うことで、ユーザAがPost200にアクセスできないようにすることができます。

django-guardianは、Django組み込みのパーミッションと似たインターフェースで使うことができます。

>>> from django.contrib.auth.models import User
>>> from guardian.shortcuts import assign_perm
>>> from app.models import Post
>>> 
>>> john = User.objects.create(username='john')
>>> post = Post.objects.create(content='This is the content.', author=john)
>>> john.has_perm('view_content', post)
False
>>> assign_perm('view_content', john, post)
>>> john.has_perm('view_content', post)
True

パーミッションの設定はユーサだけでなくグループに対しても行うことができます。

>>> from django.contrib.auth.models import Group
>>> group = Group.objects.create(name='editors')
>>> assign_perm('view_content', group, post)
>>> bob = User.objects.create(username='bob')
>>> bob.groups.add(group)
>>> bob.has_perm('view_content', post)
True

django-guardianにはこれ以外にもデコレータやテンプレートタグなどの便利な機能を提供しています。詳細は公式ドキュメントで確認してください。

django-guardian.readthedocs.io

django-role-permissions

django-role-permissionsは、ロールベースでパーミッションを管理するためのアプリケーションです。どのユーザがどのオブジェクトにアクセスできるのかを個別に設定するのではなく、ロールを定義してそれに基づいてパーミッションを管理します。

from rolepermissions.roles import AbstractUserRole

class Writer(AbstractUserRole):
    available_permissions = {
        'create_content': True,
        'view_content': True,
    }

class Reader(AbstractUserRole):
    available_permissions = {
        'create_content': False,
        'view_content': True,
    }

ユーザーにロールを割り当てたら、そのユーザーのロールとパーミッションについて確認することができます。

>>> from rolepermissions.checkers import has_permission, has_role
>>> from rolepermissions.roles import assign_role
>>> has_role(john, Writer)
False
>>> assign_role(john, Writer)
<class 'Writer'>
>>> has_role(john, Writer)
True
>>> has_permission(john, 'create_content')
True

オブジェクトレベルの権限のチェックは関数を定義して行うことができます。

from rolepermissions.permissions import register_object_checker

@register_object_checker()
def edit_content(role, user, obj):
    if role == Writer:
        return True
    if obj.author == user:
        return True
    return False

呼び出すときは以下のようにします。

>>> from rolepermissions.checkers import register_object_checker
>>> has_object_permission('edit_content', john, post)

django-role-permissionsは、紹介した以外にもミックスインやデコレータ、テンプレートタグなどの便利な機能を提供しています。詳細は公式ドキュメントで確認してください。

django-role-permissions.readthedocs.io

django-rules

django-rulesも、オブジェクトレベルのパーミッションを実装できるアプリです。特徴としては、パーミッションの管理にデータベースモデルを使用しない点を挙げられます。

まずは真理値を返す関数を定義します。この際、@rules.predicateデコレータを付ける必要があります。

@rules.predicate
def is_post_writter(user, obj):
    return obj.author == user

次に、定義した関数をパーミッションと結びつけます。

rules.add_rule('can_edit_post', is_post_writer)

そして最後に、オブジェクトを使ってパーミッションをテストします。

>>> rules.test_rule('can_edit_post', john, post)
True

おわりに

本記事で紹介したアプリケーション以外にもDjangoには多くのアクセス制御のためのサードパーティアプリケーションがあります。その比較に関しては以下のサイトを参照するのが良いと思います。

djangopackages.org

参考資料