# Authorization for Django, like Pyramid @hirokiky http://slides.hirokiky.org/ploneconf2018.html --- class: middle ## Welcome to Japan --- class: middle ## Thanks for this awesome event --- ## me, mainly Django * I know about Django well * But not about Pyramid and Plone I want to share knowledges. --- ## About hirokiky * Call me KY * hirokiky.org * github.com/hirokiky * Chairperson of DjangoCongress JP --- class: small-image ## Working * Working for "BeProud" * I'm creating PyQ (https://pyq.jp/) ![beproud](./images/beproud.png) --- ## Why Pyramid * The architechure is interesting * I learned way to use Pyramid and some repoze utilities --- ## About * Pyramid's authn/authz system * Django's permission handling * django library for better Authorization --- ## requirements.txt ```python web>="intermediate" ``` --- class: middle ## Authn / Authz --- ## Authn / Authz * Authentication: 認証 (Nin-sho) * Authorization: 認可 (Nin-ka) --- ## Authentication **WHO** you are * Login * User --- ## Authorization **WAHT** you can * Permission --- class: middle ## Pyramid Security --- ## Authn / Authz Policy Pyramid can change both policy. like... * `AuthTktAuthenticationPolicy` * `ACLAuthorizationPolicy` --- ## Authn Policy Detects users and/or **principals** from request --- ## Authz Policy Allow/Deny from **context**, and **principals**, **permission** --- ## Words * principals: Authn-ed information * context: Target object * permission: *verbs* you want --- ## I meant * principals: Logged in user (Hiroki) * context: An article which id is 1 * permission: Edit (the article) --- class: middle Can **Hiroki** **Edit** **the Article**? --- class: wide, middle ![Pyramid Security](images/ploneconf2018/pyramid_security.png) --- ## ACL ```python class Article: owner = somelibrary.FKField(to=User) def __acl__(self): return [ (Allow, Everyone, 'view'), (Allow, self.owner, 'edit'), ] ``` --- ## View ```python @view_config( context=Article, permission='view', ) def article_detail(request): ... ``` Context is determined by the request's path. --- ## View ```python @view_config( context=Article, permission='edit', ) def article_edit(request): ... ``` --- ## Why ACL is nice * Without dynamic (permission) data * Authz with data not only user * Decralative style --- ## Django's basic way ```python @login_required def article_detail(request): ... ``` --- ## Django's Permission ```python content_type = ContentType.objects.get_for_model( BlogPost ) permission = Permission.objects.get( codename='change_blogpost', content_type=content_type, ) user.user_permissions.add(permission) ``` --- ## @permission dec ```python @permission_required('myapp.change_blogpost') def change_blog_post(request, blog_id): ... ``` --- ## But * I don't want to save the "permission" * Only User <-> Model permission * What will happen if I change conditions to apply permissions? --- class: middle * Most of my friends won't use permissions * Creating own view decorator for authz --- ## Like ```python from myapp.security import my_subscription_requried @my_subscription_required(settings.PERSONAL_PLAN) def article_detail(request): ... ``` --- ## thinking_face * It's OK * But there should be better way --- class: middle, center ## What I created [github.com/hirokiky/django-keeper](https://github.com/hirokiky/django-keeper/) --- ## ACL ```python from keeper.security import Allow from keeper.operators import Everyone, IsUser class Article(models.Model): author = models.ForeginKey("myapp.User") def __acl__(self): return [ (Allow, Everyone, 'view'), (Allow, IsUser(self.author), 'edit'), ] ``` --- ## View ```python 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 ... ``` --- ## ACL and View dec * ACL: Allow/Deny, Who, Permission * @keeper: Authz and data fetching --- ## Replacement of principals ````python (Allow, IsUser(self.author), 'edit') ``` It's just a `Callable[[HttpRequest], bool]` --- ## IsUser operator ```python class IsUser: def __init__(self, user): self.user = user def __call__(self, request): return self.user == request.user ``` --- ## Problems * The `Operator` approach can't separate Authn policy * But Django has `django.contrib.auth` * Additional querying may happen on request processing --- ## Replacement of contexts The `keeper` decorator will get corresponding object ```python @keeper( 'edit' Article, lambda request, article_id: {'id': article_id} ) ``` --- ## Using on templates ```django {% load keeper %} {% has_permission article 'edit' as can_edit %} {% if can_edit %}
Edit
{% endif %}
{{ article.title }}
{{ article.body }}
``` --- ## Changing error actions Not to tell "Here's secret contents". ```python from keeper.views import not_found @keeper( 'edit', ... on_fail=not_found ) ``` --- ## Root Context ```python class Root: def __acl__(self): return [ (Allow, Authenticated, ...), ... ] ``` --- ## With CBV ```python class ArticleDetail( SingleObjectPermissionMixin, DetailView): permission = 'view' on_fail = not_found ... def get_object(self): ... ``` --- ## With Rest Framework ```python class ArticleViewSet(viewsets.GenericViewSet): permission_classes = (IsAuthenticated, KeeperDRFPermission) model_permission = 'edit' ``` --- class: middle, center ## Production --- class: middle, center, middle ![PyQ1](./images/ploneconf2018/pyq_1.png) --- class: middle, center, middle ![PyQ2](./images/ploneconf2018/pyq_2.png) --- class: middle, center, middle ![PyQ3](./images/ploneconf2018/pyq_3.png) --- ## Usage: In my project * Subscribing plan * Personal users / (Business) team * Team role (member / leader / owner) * ... --- ```python def __acl__(self): if self.public: acl = [ (Allow, Everyone, 'view'), (Allow, IsAttribute( 'user.subscription.plan', foobar ), 'learn'), ] else: acl = [ (Allow, IsAttribute( 'user.team', self.team ), ('view', 'learn', 'edit')), ... ] return acl + [ ... ] ``` --- ## How about it * Easy to handle permission * Easy to change Authz --- ## Conclution * Pyramid has interesting security system * Django has basic User/Authn system * django-keeper provide (alt) Authz system --- ## That's all folks Thanks you. * [@hirokiky](https://twitter.com/hirokiky/) * [github.com/hirokiky](https://github.com/hirokiky/)