Django + django-rules + 独自Userモデルで、has_permテンプレートタグを使うときの注意点

Djangoには標準で認可(Permission)の仕組みがあります。

ただ、Django標準の認可はモデルレベルです。オブジェクトレベルは

Djangoパーミッションフレームワークはオブジェクトパーミッション基盤を持っていますが、コアには実装されていません。

https://docs.djangoproject.com/ja/3.0/topics/auth/customizing/#handling-object-permissions

と公式ドキュメントにあります。

 
そのため、オブジェクトレベルで認可を扱いたい時はライブラリを使います。その選択肢の一つとして django-rules があります。
dfunckt/django-rules: Awesome Django authorization, without the database

 
そんな中、Django + Django-rules + 自作UserモデルのDjangoアプリにて、django-rulesの has_perm テンプレートタグを使おうとした時にハマったため、メモを残します。

 
目次

   

環境

 

問題

PermissionsMixinを継承せずに独自Userモデルを作成したところ、django-rulesによる認可が動作しませんでした。

 

再現方法
独自Userモデルの作成

Django標準のUserモデルではなく、 AbstractBaseUser を継承して独自Userモデルを作成しました。

独自Userモデルを作成する場合、 PermissionsMixin も継承しておくことで、パーミッションまわりの機能を独自ユーザーへ簡単に組み込めます。
https://docs.djangoproject.com/ja/3.0/topics/auth/customizing/#custom-users-and-permissions

ただ、この時は

  • このDjangoアプリが検証目的だったこと
  • django-rulesのREADMEには独自Userモデルのことが書かれていなかったこと

より、 PermissionsMixin を継承しない独自Userモデルを作成しました。

class User(AbstractBaseUser):
    pass

 

INSTALLED_APPS

ドキュメントには

  • rules
  • rules.apps.AutodiscoverRulesConfig

のどちらかを指定するよう記載されています。

ただ、 rules.apps.AutodiscoverRulesConfig を指定しておくと、django-rulesが自動的に rules.py という名前のファイルを探してくれるため、 rules.apps.AutodiscoverRulesConfig を指定します。

rules may optionally be configured to autodiscover rules.py modules in your apps and import them at startup. To have rules do so, just edit your INSTALLED_APPS setting:

https://github.com/dfunckt/django-rules#best-practices

 

AUTHENTICATION_BACKENDS

認証バックエンドとしてdjango-rulesのものが必要なため、settings.pyに追加しておきます。

AUTHENTICATION_BACKENDS = (
    'rules.permissions.ObjectPermissionBackend',
    'django.contrib.auth.backends.ModelBackend',
)

 

rules.py

django-rules用に、 rules.py にルールを実装しました。

import rules

@rules.predicate
def is_admin(user):
    return user.is_admin

rules.add_perm('accounts.admin', is_admin)

 

テンプレートでdjango-rulesを利用

rules をloadした後、 has_perm で権限により表示/非表示を切り替えます。

{% load rules %}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>お知らせ</title>
</head>
<body>
<h3>お知らせ一覧</h3>
<ul>
    {% has_perm 'accounts.admin' request.user as is_admin %}
    {% if is_admin %}
        <li>システム管理者だけが見える</li>
    {% endif %}
<ul>

 
ここまででdjango-rulesの設定が終わっているものの、runserverしても has_perm テンプレートタグが動作しませんでした。

 

原因

django-rulesのテンプレートタグ has_permソースコードを見たところ、

@register.simple_tag
def has_perm(perm, user, obj=None):
    if not hasattr(user, 'has_perm'):  # pragma: no cover
        return False  # swapped user model that doesn't support permissions
    else:
        return user.has_perm(perm, obj)

# https://github.com/dfunckt/django-rules/blob/v2.2.0/rules/templatetags/rules.py#L15

と書かれていました。

テンプレートタグ has_perm は、Userモデルに has_perm() メソッドがある前提で動作するようです。

そのため、 PermissionsMixin を継承しないなどでUserモデルに has_perm() メソッドが無い場合は、常に False を返します。

その結果、rules.pyに設定したコードは動作しなかったようです。

 

対応

簡単な対応としては、を継承した独自Userモデルでは PermissionsMixin を継承するようにします。

class User(AbstractBaseUser, PermissionsMixin):
    pass

 
もし、 PermissionsMixin を継承できない場合にテンプレートタグ has_perm を使いたい場合は、何らかの形で独自ユーザークラスに has_perm メソッドを実装しておきます。

 
なお、AbstractBaseUser ではなく AbstractUser を継承した独自ユーザーの場合は PermissionsMixin を継承済のため、このようなことは起きません。
https://github.com/django/django/blob/3.0.5/django/contrib/auth/models.py#L316