delhi09の勉強日記

技術トピック専用のブログです。自分用のメモ書きの投稿が多いです。あくまで「勉強日記」なので記事の内容は鵜呑みにしないでください。

Djangoのビルトインのパーミッション機能の仕組みを理解する

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.

docs.djangoproject.com

従って、最終的には自分が書いたコードで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図を書いてみたところ、以下のようになった。

f:id:kamatimaru:20200514062523p:plain

django_content_type」とは何か?

上記のテーブル一覧の中で「django_content_type」が何なのかよく分からなかった。
(他のテーブルに関してはテーブル名とテーブル定義から大体想像できた。)


・「django_content_type」のテーブル定義は以下

No カラム名 カラムタイプ
1 id integer
2 app_label varchar(100)
3 model varchar(100)


調べてみたところ、Djangoには「contenttypes」という概念が存在するらしい。
以下が公式ドキュメント

docs.djangoproject.com

公式ドキュメントによると、「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」と多対一の関係である。

Djangoパーミッションはモデル単位と言われている理由はこの辺にあるのかなと思った。

データフロー

以下、データフローの検証に入っていく。

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

変更のあったテーブル

最初のマイグレーションを実行すると、以下の2つのテーブルにデータが作成された。

  • django_content_type
  • auth_permission

変更の内容

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')

github.com

2.superuser作成時

オペレーション

以下のコマンドでsuperuserを作成する。

$ python manage.py createsuperuser

変更のあったテーブル

  • auth_user

変更の内容

auth_user

以下のようにユーザーデータが作成される。

id password last_login is_superuser username first_name email 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ユーザーでログインする。

変更のあったテーブル

なし

画面の状態

以下のようにGroups・Users・Questionモデルに対してそれぞれ「Add」・「Change」ボタンが表示されている。

f:id:kamatimaru:20200514212339p:plain

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.」と表示されるようになったので、この仮説は正しいと考えられる。

f:id:kamatimaru:20200514032232p:plain

追加オペレーション

さらに検証するために、「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」ボタンが表示されるようになった。
f:id:kamatimaru:20200514034424p:plain

※ 手動で更新したデータを元に戻す。

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」というユーザーを管理画面から作成する。

f:id:kamatimaru:20200514035211p:plain

管理画面にログインするためには、「Staff status」にチェックを入れる必要がある。

f:id:kamatimaru:20200514043130p:plain

「yamada_tarou」ユーザーでログインし直す。

「You don't have permission to view or edit anything.」と表示される。

f:id:kamatimaru:20200514043539p:plain


「admin」ユーザーでログインし直して、「yamada_tarou」ユーザーに「Question」モデルへのCRUD権限を付与する。

f:id:kamatimaru:20200514044406p:plain

「yamada_tarou」ユーザーでログインし直す。

画面の状態

「Question」モデルの「Add」・「Change」ボタンが表示されるようになった。

f:id:kamatimaru:20200514045633p:plain

データの状態

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権限のパーミッションのグループを作成する。

f:id:kamatimaru:20200514054937p:plain

「yamada_tarou」に「user_ctrl」グループを割り当てる。

f:id:kamatimaru:20200514055204p:plain

「yamada_tarou」ユーザーでログインし直す。

画面の状態

「User」モデルの「Add」・「Change」ボタンが表示されるようになった。

f:id:kamatimaru:20200514055417p:plain

データの状態

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>

まとめ

Djangoのビルトインのパーミッション機能の仕組み(データフロー)が大体理解できた。

元々がadminサイト用のパーミッションということがあるのかもしれないが、パーミッションの単位がモデルという制約があるので、流用できる(した方がいい)場合と流用できない(しない方がいい)場合がありそうだなと思った。

参考

以下の発表動画及びスライドを参考にさせて頂いた。

youtu.be

gitpitch.com