Djangoでアプリケーションを作っているとアクセス制御をしたくなることがあります。たとえば、会員サイトではプレミアムユーザと一般ユーザによってアクセスできる情報に差を付けたいことがあるでしょう。こういった機能は、少し規模の大きなサイトではよく見かけます。
Djangoでアクセス制御をする方法はいくつもあります。その中にはDjango組み込みの方法やアプリケーションとして提供されている方法もあります。本記事ではそれらを比較してどのように使えるのか、どういった場面に向いてそうなのかといったことを確認します。
本記事の構成は以下の通りです。
Django組み込みのパーミッション
Django組み込みのパーミッションは、ユーザかグループに適用できるモデルレベルのパーミッションです。INSTALLED_APPS
にdjango.contrib.auth
がある場合、Djangoはmigrate
を実行したタイミングで、自動的にすべてのモデルに対して、閲覧(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」から行うことができます。
テンプレートから権限を参照したい場合、コンテキストに自動的に追加される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
を実行したときに自動的に権限が作成されます。
より詳細な話はDjangoの公式ドキュメントを確認してください。
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には多くのアクセス制御のためのサードパーティアプリケーションがあります。その比較に関しては以下のサイトを参照するのが良いと思います。