Djangoのビルトインのパーミッション機能について、仕組みを理解するために記事を書く。
※ Djangoのパーミッションの仕組みに関する公式ドキュメントは以下
docs.djangoproject.com
目的
公式ドキュメントには、Djangoのビルトインのパーミッション機能はDjangoのadminサイトで使用されているが、自分が書いたコードでも使うことができると記載されている。
Django comes with a built-in permissions system. It provides a way to assign permissions to specific users and groups of users.
It’s used by the Django admin site, but you’re welcome to use it in your own code.
従って、最終的には自分が書いたコードでDjangoのパーミッション機能を使えるようになりたい。
ただし、まずは仕組みを理解しないと使うことができないので、仕組みを理解することを第一の目的とする。
方針
Djangoのパーミッション機能に関するデータはDB(SQLite)に保存されるので、まずはデータフローを理解する必要がある。
パーミッションに関するテーブルのデータが変更されるのは、以下のオペレーションを実施した時である。
従って、マイグレーションを実行したり、Djangoの管理サイトを操作したりしながら、その都度SQLite内のデータを確認することで、データフローを理解していく方針とする。
Djangoのパーミッション機能に関係のあるテーブル一覧
Djangoのパーミッション機能に関係のあるテーブル一覧は以下の7つである。
- auth_group
- auth_group_permissions
- auth_permission
- auth_user
- auth_user_groups
- auth_user_user_permissions
- django_content_type
ER図
テーブル名・テーブル定義からテーブル間の関係を推測してER図を書いてみたところ、以下のようになった。
「django_content_type」とは何か?
上記のテーブル一覧の中で「django_content_type」が何なのかよく分からなかった。
(他のテーブルに関してはテーブル名とテーブル定義から大体想像できた。)
・「django_content_type」のテーブル定義は以下
No | カラム名 | カラムタイプ |
---|---|---|
1 | id | integer |
2 | app_label | varchar(100) |
3 | model | varchar(100) |
調べてみたところ、Djangoには「contenttypes」という概念が存在するらしい。
以下が公式ドキュメント
公式ドキュメントによると、「app_label」には該当のモデルが属しているアプリケーション名、「model」にはモデルのClass名(小文字)が入るとのことである。
例えばDjangoチュートリアル に倣って「polls」アプリケーションに「Question」モデルと「Choice」モデルを定義した場合、マイグレーションを実行すると以下のようなレコードが作成される。
[django_content_type]
id | app_label | model |
---|---|---|
7 | polls | question |
8 | polls | choice |
以上より、いったんはモデルをマスター管理するためのテーブルと理解しておく。
また、先のER図を見ると分かるように、「auth_permission」は「django_content_type」と多対一の関係である。
データフロー
以下、データフローの検証に入っていく。
1.Djangoプロジェクト作成時
オペレーション
以下のコマンドでDjangoプロジェクトを作成する。
$ django-admin startproject samplepj
以下のコマンドでマイグレーションを実行する。
$ cd samplepj
$ python manage.py makemigrations
No changes detected
$ python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying sessions.0001_initial... OK
変更の内容
django_content_type
以下の6レコードが作成される。
id | app_label | model |
---|---|---|
1 | admin | logentry |
2 | auth | permission |
3 | auth | group |
4 | auth | user |
5 | contenttypes | contenttype |
6 | sessions | session |
auth_permission
以下の24レコードが作成される。
id | content_type_id | codename | name |
---|---|---|---|
1 | 1 | add_logentry | Can add log entry |
2 | 1 | change_logentry | Can change log entry |
3 | 1 | delete_logentry | Can delete log entry |
4 | 1 | view_logentry | Can view log entry |
5 | 2 | add_permission | Can add permission |
6 | 2 | change_permission | Can change permission |
7 | 2 | delete_permission | Can delete permission |
8 | 2 | view_permission | Can view permission |
9 | 3 | add_group | Can add group |
10 | 3 | change_group | Can change group |
11 | 3 | delete_group | Can delete group |
12 | 3 | view_group | Can view group |
13 | 4 | add_user | Can add user |
14 | 4 | change_user | Can change user |
15 | 4 | delete_user | Can delete user |
16 | 4 | view_user | Can view user |
17 | 5 | add_contenttype | Can add content type |
18 | 5 | change_contenttype | Can change content type |
19 | 5 | delete_contenttype | Can delete content type |
20 | 5 | view_contenttype | Can view content type |
21 | 6 | add_session | Can add session |
22 | 6 | change_session | Can change session |
23 | 6 | delete_session | Can delete session |
24 | 6 | view_session | Can view session |
上記のデータを見ると分かるように、
("add", "change", "delete", "view") × django_content_type.model
の掛け合わせとなっている。(4 × 6で24通り)
「add」・「change」・「delete」・「view」というパーミッションに関しては、Django本体のソースを確認したところ、「django.db.models.options.Options」にデフォルトパーミッションとして定義されていた。
self.default_permissions = ('add', 'change', 'delete', 'view')
2.superuser作成時
オペレーション
以下のコマンドでsuperuserを作成する。
$ python manage.py createsuperuser
変更のあったテーブル
- auth_user
変更の内容
auth_user
以下のようにユーザーデータが作成される。
id | password | last_login | is_superuser | username | first_name | is_staff | is_active | date_joined | last_name | |
---|---|---|---|---|---|---|---|---|---|---|
1 | 省略 | Null | 1 | admin | Null | admin@example.com | 1 | 1 | 2020-05-13 08:26:30.022110 | Null |
3.モデル作成時
オペレーション
公式のDjangoチュートリアル に従って、「Question」モデルと「Choice」モデルを作成する。
from django.db import models class Question(models.Model): question_text = models.CharField(max_length=200) pub_date = models.DateTimeField('date published') class Choice(models.Model): question = models.ForeignKey(Question, on_delete=models.CASCADE) choice_text = models.CharField(max_length=200) votes = models.IntegerField(default=0)
マイグレーションを実行する。
$ python manage.py makemigrations polls $ python manage.py migrate
変更のあったテーブル
- django_content_type
- auth_permission
変更の内容
django_content_type
id | app_label | model |
---|---|---|
7 | polls | question |
8 | polls | choice |
「Question」モデルと「Choice」モデルが追加されている。
auth_permission
id | content_type_id | codename | name |
---|---|---|---|
25 | 7 | add_question | Can add question |
26 | 7 | change_question | Can change question |
27 | 7 | delete_question | Can delete question |
28 | 7 | view_question | Can view question |
29 | 8 | add_choice | Can add choice |
30 | 8 | change_choice | Can change choice |
31 | 8 | delete_choice | Can delete choice |
32 | 8 | view_choice | Can view choice |
「Question」モデルと「Choice」モデルに関する「add」・「change」・「delete」・「view」のパーミッションが追加されている。
4.adminサイトにモデルを追加して管理サイトにアクセスした時
オペレーション
以下のようにadminサイトに「Question」モデルを追加する。
[polls/admin.py]
from django.contrib import admin from .models import Question admin.site.register(Question)
管理サイトにアクセスし、先ほど作成したadminユーザーでログインする。
変更のあったテーブル
なし
5.「auth_user.is_superuser」を「false」に書き換えてみる。
4において、「auth_user_user_permissions」にも「auth_user_groups」にもレコードは存在しなかった。
→ 「admin」ユーザーにデータ上でパーミッションが付与されている訳ではない。
しかし、「admin」ユーザーでログインすると、「Add」・「Change」ボタンが表示されていた。
以上より、「auth_user.is_superuser」が「true」ならば、ユーザーに付与されている権限に関係なく全ての権限が付与されている扱いになる仕様なのではないかと思った。
上記の仮説を検証してみる。
オペレーション
「auth_user」テーブルの「admin」ユーザーの「is_superuser」を「false」にupdateする。
sqlite> select is_superuser from auth_user where id = 1; 1 sqlite> update auth_user set is_superuser = false where id = 1; sqlite> select is_superuser from auth_user where id = 1; 0 sqlite>
再度管理画面にアクセスする。
画面の状態
以下のように、「You don't have permission to view or edit anything.」と表示されるようになったので、この仮説は正しいと考えられる。
追加オペレーション
さらに検証するために、「auth_user_user_permissions」に「add_question」のパーミッションを許可するレコードをinsertしてみて、「Add」ボタンが表示されるようになるか確認する。
sqlite> select * from auth_permission where codename = 'add_question'; 25|7|add_question|Can add question sqlite> insert into auth_user_user_permissions (user_id, permission_id) values(1, 25); sqlite> select * from auth_user_user_permissions; 1|1|25 sqlite>
再度、管理画面にアクセスする。
画面の状態
以下のように「Question」モデルの「Add」ボタンが表示されるようになった。
※ 手動で更新したデータを元に戻す。
sqlite> select is_superuser from auth_user where id = 1; 0 sqlite> update auth_user set is_superuser = true where id = 1; sqlite> select is_superuser from auth_user where id = 1; 1 sqlite> select * from auth_user_user_permissions; 1|1|25 sqlite> delete from auth_user_user_permissions where id = 1; sqlite> select * from auth_user_user_permissions; sqlite>
6.admin以外のユーザーを作成してユーザー権限を付与してみる。
オペレーション
「yamada_tarou」というユーザーを管理画面から作成する。
管理画面にログインするためには、「Staff status」にチェックを入れる必要がある。
「yamada_tarou」ユーザーでログインし直す。
「You don't have permission to view or edit anything.」と表示される。
「admin」ユーザーでログインし直して、「yamada_tarou」ユーザーに「Question」モデルへのCRUD権限を付与する。
「yamada_tarou」ユーザーでログインし直す。
データの状態
DBのデータを確認すると、データ上でも権限が付与されていることが確認できる。
sqlite> select id, username from auth_user; 1|admin 5|yamada_tarou sqlite> select * from auth_user_user_permissions where user_id = 5; 2|5|25 3|5|26 4|5|27 5|5|28 sqlite> select * from auth_permission where id in (25, 26, 27, 28); 25|7|add_question|Can add question 26|7|change_question|Can change question 27|7|delete_question|Can delete question 28|7|view_question|Can view question sqlite>
7.グループを作成・付与してみる。
「6」ではユーザー単位のパーミッションを試したので、次に「group」を作成して、「yamada_tarou」ユーザーに割り当ててみる。
オペレーション
「admin」ユーザーでログインして、「user_ctrl」というグループ名でユーザーのCRUD権限のパーミッションのグループを作成する。
「yamada_tarou」に「user_ctrl」グループを割り当てる。
「yamada_tarou」ユーザーでログインし直す。
データの状態
DBのデータを確認すると、データ上でも権限が付与されていることが確認できる。
sqlite> select * from auth_group; 1|user_ctrl sqlite> select * from auth_group_permissions; 1|1|16 2|1|13 3|1|14 4|1|15 sqlite> select * from auth_permission where id in (13, 14, 15, 16); 13|4|add_user|Can add user 14|4|change_user|Can change user 15|4|delete_user|Can delete user 16|4|view_user|Can view user sqlite> select * from auth_user_groups; 1|5|1 sqlite>