Skip to main content

PyCASL is the pythonized version of http://casl.js.org

Define abilities

Lets define Ability for a blog website where visitors:

  • can read blog posts
  • can manage (i.e. do anything) own post
  • cannot delete a post if it was created more than a day ago
from datetime import datetime
from casl import AbilityBuilder

def define_ability_for(user):
    ability_builder = AbilityBuilder()
    ability_builder.can('read', 'BlogPost')
    ability_builder.can('manage', 'BlogPost', fields={'author': user.id})
    ability_builder.cannot('delete', 'BlogPost', conditions=[
            lambda post: post.created_at < datetime.utcnow() - datetime.timedelta(days=1)
        ])
    return ability_builder.build()

Do you see how easily business requirements were translated into CASL's rules?

Note: you can use class instead of string as a subject type (e.g., can('read', BlogPost))

Check abilities

import datetime

class BlogPost(object):
    def __init__(self, title, author, created_at=None):
        self.title = title
        self.author = author
        self.created_at = created_at
        if created_at is None:
            self.created_at = datetime.utcnow()


user = get_loggedin_user()
ability = define_ability_for(user)

ability.can('read', 'BlogPost')  # True
ability.can('read', BlogPost)  # True

ability.can('manage', BlogPost('CASL', user.id))  # True

a_week_ago = datetime.utcnow() - datetime.timedelta(weeks=1)

ability.can('delete', BlogPost('CASL', user.id))  # True
ability.can('delete', BlogPost('CASL', user.id, a_week_ago))  # False

Example with flask

from casl import AbilityBuilder
from flask import _request_ctx_stack, g, request
from flask_restful import abort, fields, marshal

# Flask extension - https://flask.palletsprojects.com/en/1.1.x/extensiondev/
class FlaskAbility(object):
    def __init__(self):
        self.__define_abilities = None

    def init_app(self, app):
        app.before_request(self.init_abilities)

    def define_for(self, f):
        self.__define_abilities = f

    def init_abilities(self):
        request_context = _request_ctx_stack.top
        ability_builder = AbilityBuilder()
        if self.__define_abilities is not None:
            self.__define_abilities(ability_builder.can, ability_builder.cannot)
        casl_ability = ability_builder.build()
        setattr(request_context, 'casl_ability', casl_ability)
        g.ability = casl_ability

    def can(self, action, subject):
        def wrap(f):
            def decorated(*args, **kwargs):
                ctx = _request_ctx_stack.top
                ability = getattr(ctx, 'casl_ability')
                if not ability.can(action, subject):
                    abort(403, message='Access not allowed.')
                return f(*args, **kwargs)

            return decorated

        return wrap

app = create_app()

def define_abilities(can, cannot):
    user = request.user
    can('read', 'BlogPost')
    can('manage', 'BlogPost', fields={'author', user.id})

ability = FlaskAbility()
ability.init_app(app)
ability.define_for(define_abilities)

post_fields = {
    'title': fields.String,
    'author': fields.String,
    'createdAt': fields.DateTime(dt_format='rfc822')
}

@app.route('/post/<string:post_id>', methods=['GET'])
@ability.can('read', 'BlogPost')
def get_blogpost(post_id):
    blogpost = find_blogpost(post_id)
    return marshal(blogpost, fields=post_fields), 200


@app.route('/post/<string:post_id>', methods=['PUT'])
def put_blogpost(post_id):
    blogpost = find_blogpost(post_id)
    if g.ability.cannot('manage', blogpost):
        abort(403, message='Access not allowed')
    ...
    update_blogpost(post_id, blogpost)
    return marshal(blogpost, fields=post_fields), 200

Metadata

Release files for PyCASL 1.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for PyCASL 1.0.1
File Size Uploaded
PyCASL-1.0.1.tar.gz 4.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for PyCASL 1.0.1
File Interpreter ABI Platform
PyCASL-1.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 8.6 kB

Release files / PyCASL-1.0.1.tar.gz

Download URL PyCASL-1.0.1.tar.gz
Size 4.4 kB
Tags Source
SHA-256 checksum
How to use checksums
0eb196a2caadca7787fee89e64d1cff0def2491b80ad7503e92e4149c5b87c2f
BLAKE2b-256 checksum
How to use checksums
fbf8323c0086e7fd5f16f90106586dbecc1ea90cbb3d36ee20c2327c19ef79ee
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.4.1 importlib_metadata/4.5.0 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.60.0 CPython/3.9.5

Release files / PyCASL-1.0.1-py3-none-any.whl

Download URL PyCASL-1.0.1-py3-none-any.whl
Size 4.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
91e2c5f20d5f88142b9650cf93cac34505581833ca89a107566db2fe1400164c
BLAKE2b-256 checksum
How to use checksums
1442a879c81460a9bfafa8813d62967d5949dbfe3181331de5efdbe3a412ca57
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/3.4.1 importlib_metadata/4.5.0 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.60.0 CPython/3.9.5

Release history Release notifications | RSS feed

This release

1.0.1 This release

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page