Make組ブログ

Python、Webアプリや製品・サービス開発についてhirokikyが書きます。

DjangoでDB非依存に権限管理できるライブラリーdjango-keeperを作りました

f:id:hirokiky:20170903164740p:plain

Djangoで権限管理ってどうやっていますか?

Django自体が持つGroupやPermissionはイマイチ業務では使えないというのが実際のところなのではと思います。

そんな悩みを解決するために django-keeper というライブラリーを作りました。

こんな悩みに:

  • DBで権限を管理したくない
  • 権限を見通しの良いリストやマッピングで定義したい
  • ModelやUserに依存しない グローバルで汎用的な権限も扱いたい
  • ViewやTemplateでも使いたい

django-keeperって?

以下のようにACL(AccessControlList)というメソッドによって「権限」を決定します。

from django.conf import settings
from keeper.security import Allow
from keeper.operators import Everyone, Authenticated, IsUser


class Issue(models.Model):
    author = models.ForeignKey(settings.AUTH_USER_MODEL)
    ...

    def __acl__(self):
        return [
            (Allow, Everyone, 'view'),
            (Allow, Authenticated, 'add_comment'),
            (Allow, IsUser(self.author), 'edit'),
        ]

これで、以下のような権限を決定します。

  • すべてのリクエストには "view" 権限
  • 認証ユーザーには "add_comment" 権限
  • この Issueauthor で認証している場合は "edit" 権限

というように __acl__ メソッドによって、リクエストがこのモデルに持つ権限を決められます。

Viewで使おう

以下のようにViewで使います。

  • @keeper デコレーターを適用
  • 第一引数: 必要な権限
  • model=引数: Viewの対象のモデル
  • mapper=引数: モデルを取得する上限を返す関数
    • 引数: View関数と同じ引数
    • 戻り値: objects.get(id=issue_id) のようにキーワード引数として渡す辞書
from keeper.views import keeper


# Model Permissions
@keeper(
    'view',
    model=Issue,
    mapper=lambda request, issue_id: {'id': issue_id},
)
def issue_detail(request, issue_id):
    request.k_context  # 取得したModelが取れます。
    ...

これで @keeper が自動で Issueインスタンスを取得して、リクエストが "view" 権限を持つかをチェックします。 権限が無い場合は403となります(@keeperon_fail= 引数で権限が無い場合の挙動も変更できます)。

また、 request.k_context@keeper が取得したオブジェクトが取れます。

Operator

ACLに設定した Authenticated などはOperatorというもので、リクエストがこの条件を満たすときに権限を追加したりできます。

例えば、リクエストが認証済みの場合 "view" を付与。

    (Allow, Authenticated, "view"),

このOperatorというものは keeper.operators 内にいくつかあります。

自作のOperator

自作する場合も簡単で、Operatorは単に Callable[[HttpRequest], bool] なのですぐ作れます。

例えばIPアドレスからのアクセスかどうかを判定するOperatorはこうなります。

class IsIP:
    def __init__(self, ip):
        self.ip = ip
        
    def __call__(self, request):
        return request.META.get('REMOTE_ADDR') == self.ip

この自作OperatorもACL内で使えます。 自社のIPアドレスからアクセスした場合に強い権限を与えるようにしておくなどできそうです。

MY_IP = "..."

class Issue(models.Model):
    def __acl__(self):
        return [
            (Allow, Everyone, 'view'),
            (Allow, IsIP(MY_IP), 'edit'),
        ]

このように django-keeper はUserに依存しない条件(今回はIPアドレス)なども使って権限の管理ができます。 また、Operatorという形で「リクエストが何者かを判別する処理」と「権限」を分離できるので変更に強いですし、見通しも良いです。

他にも

django-keeper は他にも

  • モデルに依存しないグローバルな権限を扱えます
  • テンプレート内でも has_permission という条件を使えます
  • 権限のないリクエストの場合の処理を変更できます
  • 権限のDenyも設定できます

詳しくは以下から見てみてください。

PyConJP2017で発表します

来る PyConJP 2017 でも django-keeper を交えた発表をします。

発表の内容:

  • Djangoでの権限管理をする定石
  • 検討したライブラリー
  • django-keeperの紹介

をします。 以下の日程ですのでぜひ来てください。

要注意

django-keeper はまだベータというレベルのライブラリーです。

本番環境にガッツリ導入するというのは避けたほうがいいです

導入する場合は、権限チェック周りのテストをちゃんと書くことをオススメします。 ちょっと使ってみて、不備があったり足りない機能があれば教えてください。 まだ自分としても「実験段階」、「こんなものがあったらいいなぁ」という段階なので、何かあれば教えてください PyConJPのパーティーなどで権限周りの定石や悩みを共有できたら良いなぁとも思っています。

おわりに

まだまだ作っている段階のライブラリーですが、僕自身が仕事をしていて欲しかったものを考えて作っています。 他にも良い方法や機能の提案、バグ報告や他オススメのライブラリーがあればぜひ教えてください。

できれば、ぜひPyConJP 2017でお会いしましょう。