Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

プロダクト開発してわかったDjangoの深〜いパーミッション管理の話 @ PyconJP2017

3,723 views

Published on

PyQ https://pyq.jp/ というプロダクトをリリース、開発していて分かったパーミッション、認可の深い話です。
p

Published in: Technology
  • Be the first to comment

プロダクト開発してわかったDjangoの深〜いパーミッション管理の話 @ PyconJP2017

  1. 1. Djangoの深〜いパーミッシ ョン管理の話 @hirokiky http://slides.hirokiky.org/pyconjp2017.html
  2. 2. 対象の方 Pythonを使う人 Djangoを使う人 チュートリアルやった 自分でアプリ作った
  3. 3. 権限管理の悩み Viewのif文で権限の判定つらい Templateでも判定必要 自前のデコレーターで管理が破綻 DjangoのPermissionは使わない
  4. 4. Agenda 第1部: Djangoでの権限管理の定石紹介 第2部: 検討したライブラリー 第3部: こんなライブラリー作った
  5. 5. 自己紹介 @hirokiky BeProud Inc でPyQを作っています
  6. 6. PyQという製品作っています
  7. 7. PyQは本気でプログラミングを 学べる Pythonをブラウザだけで動かして学ぶ プロフェッショナルになれるレベルで学ぶ Webや機械学習、データ分析も学べる
  8. 8. PyQリリースと成長 2017年4月にリリースして順調に成長 コンテンツ拡充(機械学習など) 機能拡張 JupyterNotebook対応 他色々な対応
  9. 9. 製品の成長と設計
  10. 10. 製品の成長と設計 まずは作ることが大切 初期は小さい設計で良い 成長に合わせて設計も成長させる
  11. 11. 設計の是正
  12. 12. 第一部 Djangoでの権限管理の定石 紹介
  13. 13. Lv1 Viewでif文 def premium_articles(request): if request.user.plan.code != PREMIUM: return HttpResponseForbidden( "プレミアム会員限定です", ) ...
  14. 14. Lv1 プロパティを使う class User(AbstructUser): ... @cached_property def is_premius(self): return self.plan.code != PREMIUM
  15. 15. Lv1 Viewでif文 def premium_articles(request): if not request.user.is_premium: return HttpResponseForbidden( "プレミアム会員限定です", ) ...
  16. 16. Lv2 デコレーターでやる場合 def premium_required(f): @wraps(f) def _wrapped(request, *args, **kwargs): if not request.user.is_premium: return HttpResponseForbidden( "プレミアム会員限定です" ) return f(request, *args, **kwargs) return _wrapped
  17. 17. デコレーターを適用 @premium_required def premium_articles(request): ...
  18. 18. 変わる問題 プレミアム会員専用だった機能が、スタンダ ード会員でも見れるように # @is_premium もう使えない def premium_articles(self): ...
  19. 19. Lv2 凝ったデコレータ def subscription_required(*plan_codes): def _dec(f): @wraps(f) def _wrapped(request, *args, **kwargs): if request.user.plan.code not in plan_code return HttpResponseForbidden(...) return f(request, *args, **kwargs) return _wrapped return _dec
  20. 20. Lv2 凝ったデコレータ @subscription_required(PREMIUM_PLAN, STANDARD_PLAN) def premium_articles(request): ...
  21. 21. Lv2までの自作の問題 仕様の変更で壊れやすい デコレーターが肥大化する、増える 結局View、Templateにも処理書いちゃう
  22. 22. 「リクエストの情報」と「権限」 が密結合なのが問題
  23. 23. 「リクエストの情報」 ユーザー user である プレミアムプランに契約している
  24. 24. 「権限」 プレミアム記事を見れる チームを管理できる
  25. 25. パーミッション作るお!
  26. 26. 「パーミッション」を文字列で Set[str] プレミアム記事を読める == "view_premius_article"
  27. 27. Lv3 パーミッションでやる class User(AbstractUser): def get_permissions(): permissions = set() if user.is_premium(): permissions.add("view_premium_articles") permissions.add("view_special_feature") return permissions def has_permission(permission): return permission in self.get_permissions()
  28. 28. Lv3 パーミッションでやる場合 @user_permission_required("view_premium_articles") def premium_articles(request): ...
  29. 29. Lv3 オブジェクトは? 記事 Articleがあるとき Article.premium: プレミアム会員用 not .premium: スタンダード、プレミアム 会員用 無料会員はすべて読めない
  30. 30. Lv3 許可設定 class Article(models.Model): def has_permission(self, user, permission): permissions = set() if self.premium and user.is_premium: permissions.add("view") if not self.premium: if user.is_premium or user.is_standard: permissions.add("view") return permission in permissions
  31. 31. Lv3 View内で使う def premium_article_detail(request, id): article = get_object_or_404(Article, id=id) if not article.has_permission(user, "view"): return HttpResponseForbidden(...)
  32. 32. パーミッションで扱う利点 仕様の変更に強くなる 「お試しユーザー」ができた 「キャンペーン利用ユーザー」ができた 「未ログインも読める記事」ができた
  33. 33. Modelだけ変える def get_permissions(user): ... if user.during_campain: permissions.remove("...") ...
  34. 34. 自作パーミッション問題 独自パーミッション管理部分の仕様が複 雑になる
  35. 35. おさらい Lv1: Viewでif文 Lv2: デコレータを作って使う Lv3: パーミッション管理する仕組み
  36. 36. でも複雑なまま。。。
  37. 37. うまいことやってくれるライブラ リーはよ。。。
  38. 38. 第2部 ライブラリーでやろう
  39. 39. 検討したライブラリー DjangoのPermission django-guardian django-rules https://djangopackages.org/grids/g/perms/
  40. 40. DjangoのPermission content_type = ContentType.objects.get_for_model(BlogP permission = Permission.objects.get( codename='change_blogpost', content_type=content_type, ) user.user_permissions.add(permission)
  41. 41. DjangoのPermission @permission_required('myapp.change_blogpost') def change_blog_post(request, blog_id): ...
  42. 42. DjangoのPermissionがあわ なかった点 DBで管理したくない User <-> Modelのパーミッションだけ パーミッションを持つ条件変えたら??
  43. 43. django-guardian 一番人気らしい。 DjangoのPermissionベース。
  44. 44. django-guardian from guardian.shortcuts import assign_perm assign_perm('view_task', joe, task) joe.has_perm('view_task', task) # True
  45. 45. django-guardian良さそうな 点 シンプルに書けそう イケてる機能 Modelが持てるパーミッション一覧 権限持ってるデータだけfilter
  46. 46. guardian合わなかった点 イケてる版DjangoのPermissionだったこと DBで管理したくない User <-> Modelのパーミッションだけ パーミッションを持つ条件変えたら??
  47. 47. django-rules >>> @rules.predicate >>> def is_book_author(user, book): ... return book.author == user ... >>> rules.add_rule('can_edit_book', is_book_author) >>> rules.add_rule('can_delete_book', is_book_author) <Predicate:is_book_author object at 0x10eeaa490>
  48. 48. django-rulesで権限チェック >>> adrian = User.objects.get(username='adrian') >>> rules.test_rule('can_edit_book', adrian, guidetodj
  49. 49. django-rules良さそうな点 DB使わずにできる デコレータ、Templateもある predicate & predicateなどで新しい predicateを作れる
  50. 50. django-rulesは良さそう かなり良さそう。 権限の一覧性が悪そう? (あんまりちゃんと 試せていない)
  51. 51. でももっとうまくできそう
  52. 52. ライブラリー作ろう! 良いアイディアがあったのでライブラリーも作 ってみた。 PyQ向けに色々設計考える => ライブラリー だこれ
  53. 53. 第3部 ライブラリー作った
  54. 54. 求めていたもの 「リクエストの情報」と「権限」分離 DBで管理しない User<->Model以外のグローバルな権限 View、Templateどこでも使える
  55. 55. 作ったもの django-keeper
  56. 56. 例えば記事の著者しか編集で きないView
  57. 57. from keeper.security import Allow from keeper.operators import Everyone from keeper.operators import IsUser class Article: author = models.ForeginKey("myapp.User") def __acl__(self): return [ (Allow, Everyone, 'view'), (Allow, IsUser(self.author), 'edit'), ]
  58. 58. keeperを使った解法 from keeper.views import keeper @keeper( 'edit' Article, lambda request, article_id: {'id': article_id} ) def article_edit(request, article_id): article = request.k_context ...
  59. 59. keeperを使った解法 ACLを設定する アクション・誰に・どの権限 Viewには @keeper をつけるだけ オブジェクトの取得もできる
  60. 60. OperatorsとPermissions 「リクエストの情報」と「権限」の分離
  61. 61. Operatorって? Callable[[HttpRequest], bool] です。
  62. 62. Permissionって? str です。
  63. 63. デフォルトのOperators keeper.operators.Everyone keeper.operators.Authenticated keeper.operators.IsUser keeper.operators.Staff
  64. 64. デフォルトのOperatros return [ (Allow, Everyone, "view"), (Allow, Authenticated, "comment"), (Allow, IsUser(self.author), ("edit", "delete")), (Allow, IsStaff, ("edit", "delete")), ]
  65. 65. 追加のOperatorを作る class IsPlan(Authenticated): def __init__(self, plan_code): self.plan_code = plan_code def __call__(self, request): if not super().__call__(request): return False return ( request.user.plan.code == self.plan_code )
  66. 66. 追加のOperatorを使う from myapp.operators import IsPlan class Article(models.Model): def __acl__(self): return [ (Allow, IsPlan(PLAN_PREMIUM), 'view'), (Allow, IsPlan(PLAN_STANDARD), 'view'), ]
  67. 67. from myapp.operators import IsPlan class Article(models.Model): premium = models.BooleanField() def __acl__(self): if self.premium: return [ (Allow, IsPlan(PLAN_PREMIUM), 'view' ] else: return [ (Allow, IsPlan(PLAN_PREMIUM), 'view' (Allow, IsPlan(PLAN_STANDARD), 'view' ]
  68. 68. ACLの利点 権限をもつ場合を俯瞰しやすい 「リクエストの情報」判定のロジックは Operator 「権限」はPermission
  69. 69. テンプレートでの利用方法 {% load keeper %} {% has_permission article 'edit' as can_edit %} {% if can_edit %} <a href="{% url ... %}">編集</a> {% endif %} <h1>{{ article.title }}</h1> <p>{{ article.body }}</p>
  70. 70. グローバルのACL設定 class Root: def __acl__(self): return [ (Allow, Authenticated, 'post_article'), ]
  71. 71. グローバル権限を使う @keeper('post_article') def article_post(request): pass
  72. 72. デモアプリ紹介 ユーザー、契約、チームなど含むアプリで django-keeperを使ったデモ。 https://github.com/hirokiky/django- keeper/tree/master/demo/
  73. 73. django-keeperの良い所 DBを使わない ACLを使うので見通しが良い User<->Modelに依存しないこと 「リクエストの情報」「権限」を分離する
  74. 74. django-keeperの悪い所 まだまだベータ感があります。。
  75. 75. 全体のまとめ Djangoでの権限管理の定石 検討したライブラリー 作ったライブラリー
  76. 76. PyQブース出展中です CC BY 2.0 PyConJP
  77. 77. おわり

×