Skip to main content

Library for managing permissions in your Python 3.7+ projects

Project description

deny

Library for managing permissions in your Python 3.7+ projects.
For example, it can be used to grant access to some API endpoints based on policies you defined.

Installation

pip install deny

Usage

First define the permissions needed in your project:

from deny import AutoPermission, Permission

class ProfilePermissions:
    view = Permission(name="view_project")
    edit = AutoPermission()  # name will be automatically set to "ProjectPermissions.edit"

class SessionPermissions:
    create = AutoPermission()
    delete = AutoPermission()

Then create policies that will be used to grant the permissions:

from deny import Policy, authorize

class LoggedInUser:
    @authorize(SessionPermissions.delete)
    async def can_logout(self):
        return True


class AnonymousUser(Policy):
    @authorize(SessionPermissions.create)
    async def can_logout(self):
        return True


class UserPolicy(LoggedInUser, Policy):
    def __init__(self, current_user_id: int):
        """You should inject any dependency this policy relies on.
        Here we just save the current user ID, but you might need 
        a connection to a database, a whole user object, etc.
        """
        self._current_user_id = current_user_id

    @authorize(ProfilePermissions.edit)
    async def can_edit_profile(self, user_id: int) -> bool:
        """Only current user can edit his own profile."""
        return self._current_user_id == user_id

    @authorize(ProfilePermissions.view)
    async def can_view_profile(self, user_id: int) -> bool:
        """Everybody can view the user profiles."""
        return True

Finally create an Ability with the right policy and check if the permissions are granted:

from deny import Ability

def get_ability(current_user_id: Optional[int]) -> Ability:
    if current_user_id:
        policy = UserPolicy(current_user_id)
    else:
        policy = AnonymousUser()

    return Ability(policy=policy)

################
# Logged in user
ability = get_ability(1)
await ability.can(ProfilePermissions.view, 1) # True
await ability.can(ProfilePermissions.view, 2) # True
await ability.can(ProfilePermissions.edit, 1) # True
await ability.authorize(ProfilePermissions.edit, 1) # does not throw any error
await ability.can(ProfilePermissions.edit, 2) # False
await ability.authorize(ProfilePermissions.edit, 2) # throw an UnauthorizedError
await ability.can(SessionPermission.create) # False
await ability.can(ProfilePermissions.delete) # True

################
# Anonymous user
ability = get_ability(None)
await ability.can(ProfilePermissions.view, 1) # False
await ability.can(ProfilePermissions.view, 2) # False
await ability.can(ProfilePermissions.edit, 1) # False
await ability.authorize(ProfilePermissions.edit, 1) # throw an UnauthorizedError
await ability.can(ProfilePermissions.edit, 2) # False
await ability.authorize(ProfilePermissions.edit, 2) # throw an UnauthorizedError
await ability.can(SessionPermission.create) # True
await ability.can(ProfilePermissions.delete) # False

You can see the full example in examples/usage.py (you will need asyncio to run it, pip install asyncio)

Web frameworks

Deny can be used with any web framework.
But it comes with some helper functions for Falcon, Sanic and FastAPI.

Here is an example for the Sanic web framework:

from sanic import Sanic
from sanic.request import Request
from sanic.response import HTTPResponse, json
from typing import Any

from deny import Ability, AutoPermission, Policy, authorize as policy_authorize
from deny.errors import UnauthorizedError
from deny.ext.sanic import authorize



class ProjectPermissions:
    view = AutoPermission()


class UserPolicy(Policy):
    @authorize(ProjectPermissions.view)
    async def can_view_project(self, request: Request, id: int) -> bool:
        return id == 1


app = Sanic("example")


@app.middleware("request")
async def inject_ability(request: Request) -> None:
    request.ctx.ability = Ability(policy=UserPolicy())


@app.get("/projects/<id:int>")
@authorize(ProjectPermissions.view)
async def get(request: Request, id: int) -> HTTPResponse:
    return json({"id": id})


@app.exception(UnauthorizedError)
async def unauthorized_handler(request: Request, exc: Exception):
    return json({"error": str(exc)}, status=403)

You can find the examples for each of those frameworks in the examples/ folder from this repository.

Sync support

By default all the classes provided by deny are built to run in an asynchronous environement.
If you run in a synchronous environement (without async, await), then import from deny.sync instead of deny.
See examples/sync.py for a full example.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

deny-0.1.1a1.tar.gz (9.7 kB view details)

Uploaded Source

Built Distribution

deny-0.1.1a1-py3-none-any.whl (12.8 kB view details)

Uploaded Python 3

File details

Details for the file deny-0.1.1a1.tar.gz.

File metadata

  • Download URL: deny-0.1.1a1.tar.gz
  • Upload date:
  • Size: 9.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.1.13 CPython/3.10.2 Linux/5.17.8-lp153.2.g718e8e9-default

File hashes

Hashes for deny-0.1.1a1.tar.gz
Algorithm Hash digest
SHA256 20153741b96e7952f9079e44cc08911c36236ea7ab35664deded3a04131d0f4e
MD5 df4cc1da868abeb8700385dddcb65fb1
BLAKE2b-256 b4cc34919897b4deca3251efcb85378e9c8992ca6c5f8e8266edffc40f5466b9

See more details on using hashes here.

File details

Details for the file deny-0.1.1a1-py3-none-any.whl.

File metadata

  • Download URL: deny-0.1.1a1-py3-none-any.whl
  • Upload date:
  • Size: 12.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.1.13 CPython/3.10.2 Linux/5.17.8-lp153.2.g718e8e9-default

File hashes

Hashes for deny-0.1.1a1-py3-none-any.whl
Algorithm Hash digest
SHA256 598881da20dfe06d55aa45e8765f89662d12b54db997bc518d7509d725ce89cb
MD5 b33f340f65e5d956bd6bd8c4a1881c8f
BLAKE2b-256 e1cf978e694ee553939b6d4a9f6a51e66027aa9ddafd003df2477c3baed98b18

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page