sqlalchemy_authorize package

Subpackages

Submodules

sqlalchemy_authorize.constants module

class sqlalchemy_authorize.constants.CRUD(value)[source]

Bases: str, Enum

Standard ‘CRUD’ actions.

CREATE = 'create'
DELETE = 'delete'
READ = 'read'
UPDATE = 'update'

sqlalchemy_authorize.permissions_mixin module

class sqlalchemy_authorize.permissions_mixin.BasePermissionsMixin(*args, protected=True, check_create=False, **kwargs)[source]

Bases: object

BaseClass to add a field-level authorization policy to a db.Model

E.g.:

class BaseUser(BasePermissionsMixin, db.Model):
    __permissions__ = BasePermissionsMixin.load_permissions(
        # Public permissions
        read=["id", "username"],

        # Role-based permissions
        self=[
            # The user can provide ``username`` and ``fullname``
            # to ``__init__`` (as keyword args) and to ``__setattr__``.
            (["create", "update"], ["username", "fullname"]),

            # The user can read/delete the entire model.
            "read",
            "delete"
        ],
        friend=[("read", ["fullname"])],  # (in addition to public "read")
        admin="*"  # i.e., all actions on all fields
    )

    id = db.Column(db.String(128), primary_key=True)
    username = db.Column(db.String(128), nullable=False)
    fullname = db.Column(db.String(128), nullable=False)
    ssn = db.Column(db.String(10), nullable=True)

Permission is denied by default (unlike other flask-authorization libraries, where you typically have to wrap functions for authorization to be checked). Also unlike other libraries, permissions are at the field level. That’s key to interoperability with libraries like graphene_sqlalchemy.

Field-level permissions imply row-level permissions. If you are allowed to update BaseUser.username, then you are assumed to have the update permission on BaseUser.

This assumes CRUD actions by default (“create”, “read”, “update”, “delete”), where:

  • “read” fields: columns, composites, properties, relationships, and methods/functions.

  • “create” & “update” fields: columns or settable properties.

  • “delete” usually isn’t concerned with individual fields.

    TODO: This will still currently fail if the method requires additional permissions.

Using BaseUser.

>>> user = BaseUser(id="123")
>>> sorted(user.roles)
['admin', 'public', 'self']
>>> sorted(user.actions)
['create', 'delete', 'read', 'update']
>>> user._protected
True
DEFAULT_ACTIONS = ['create', 'read', 'update', 'delete']
PUBLIC_ROLE = 'public'
actions

@property meets @classmethod

Source: http://stackoverflow.com/a/13624858

allow(action: Union[str, List[str]], field: Optional[Union[str, List[str]]] = None) List[str][source]

Allow action (s) on field (s).

Note

Consider using allowed() instead, to restrict additional permissions to a with statement.

Parameters
  • action – The action(s) to allow.

  • field – Which field(s) to allow action``(s) on, defaults to allow ``action on all fields.

Returns

action as list.

allowed(action: Union[str, List[str]], field: Optional[Union[str, List[str]]] = None)[source]

Allow action (s) on field (s) during the current context.

>>> user = BaseUser(id="123")
>>> user.id = "456"
Traceback (most recent call last):
PermissionError: ...
>>> user.id
'123'
>>> with user.allowed(CRUD.UPDATE.value):
...     user.id = "456"
>>> user.id
'456'
>>> with user.allowed(CRUD.UPDATE, "fullname"):
...     user.fullname = "John Doe"
...     user.username = "jdoe"
Traceback (most recent call last):
PermissionError: ...
>>> with user.allowed(CRUD.READ, ["fullname", "username"]):
...     print(user.fullname)
...     print(user.username)
John Doe
None
Parameters
  • action – The action(s) to allow.

  • field – Which field(s) to allow action``(s) on, defaults to allow ``action on all fields.

property always_allowed_fields: List[str]

Attributes that do not require authorization.

This is the opposite of :meth:authorizable_fields.

property authorizable_fields: List[str]

Attributes that should be checked for authorization

This is the opposite of :meth:always_authorized_fields.

For attributes that have been check for authorization, see authorized_fields() or authorized_fields_for().

authorize(action, key)[source]

Check whether the current user is allowed to perform action on self.model.<key>.

First checks for exceptions to normal oso rules due to allow() or deny(), otherwise passes the authorization request on to oso.

authorize_field(action: str, key: str)[source]

This is where you actually implement the check. For an example, see OsoPermissionsMixin.

Usually, you can rely on this being called indirectly (when setting/getting/deleting attributes).

This is meant as a placeholder method, not a working example, that authorizes only public actions. In practice, you’ll want to implement your role-based / relation-based / attribute-based access control here (or use a solution like oso).

Parameters
  • action – One of CRUD or a custom action.

  • key – The attribute/field to authorize.

Returns

None if the action is allowed.

Raises

:exec:`PermissionError` (or some custom error like :exec:`oso.ForbiddenError`) if not allowed.

authorized_fields(action)[source]

Returns all the authorizable_fields that the current user is allowed to perform action on.

authorized_fields_for(role: str, action: str) List[str][source]

The fields that an actor with role is allowed to perform action on.

This is a subset of authorizable_fields and won’t include fields that are in always_allowed_fields.

denied(action: Union[str, List[str]], field: Optional[Union[str, List[str]]] = None)[source]

Temporarily deny action``(s) on ``field``(s) (optionally restricted to ``field).

>>> user = BaseUser(id="123")
>>> user.id
'123'
>>> with user.denied(CRUD.READ, "id"):
...     user.id
Traceback (most recent call last):
PermissionError: ...
Parameters
  • action – The action(s) to deny.

  • field – Which field(s) to deny action``(s) on, defaults to deny ``action on all fields.

deny(action: Union[str, List[str]], field: Optional[Union[str, List[str]]] = None) List[str][source]

Deny action (s) on field (s).

Parameters
  • action – The action(s) to deny.

  • field – Which field(s) to deny action``(s) on, defaults to deny ``action on all fields.

Returns

action as list.

error(action: str)[source]

Returns an appropriate exception for the action.

This method expects to be overloaded. E.g.: OsoPermissionsMixin will raise a :exec:`ForbiddenError` for create/update/delete, but a :exec:`NotFoundError` for reads.

Returns

Permission Error (does not raise this error!)

property exempted_fields
expose()[source]

Turns off authorization.

Note

It’s recommended that you keep on authorization by default, and turn it off selectively with a context manager like exposed() or (even better) allowed().

exposed()[source]

Turns off authentication during the current context.

Note

When possible, consider more granularly relaxing permissions via allowed() to relax particular actions on particular fields.

classmethod load_permissions(*, actions=None, **kwargs)[source]

Convenience method for creating a __permissions__ dict. (You can also just pass a completed dictionary directly.)

Final permissions dictionary is of the shape:

{"<role>": {"<action>": ["field_1", "field_2"], ...}}

where passing an empty list of field is equivalent to denying that action on the entire model.

E.g.:

>>> from pprint import pprint
>>> pprint(BasePermissionsMixin.load_permissions(
...     read=["id", "username"],
...     self=[
...         (["create", "update"], ["username", "fullname"]),
...         "read",
...         "delete",
...         "custom_action"
...     ],
...     friend=[("read", ["fullname"])],
...     admin="*"
... ))
{'admin': {'create': ['*'],
           'custom_action': ['*'],
           'delete': ['*'],
           'read': ['*'],
           'update': ['*']},
 'friend': {'read': ['fullname', 'id', 'username']},
 'public': {'read': ['id', 'username']},
 'self': {'create': ['username', 'fullname'],
          'custom_action': ['*'],
          'delete': ['*'],
          'read': ['*'],
          'update': ['username', 'fullname']}}
Parameters
  • actions

    a list of actions to include (needed to expand wildcards like admin="*".)

    Defaults to CRUD + any custom actions found in the role fields.

  • read – “public” read permissions. Defaults to None, i.e., not included / forbidden at row & field levels. If provided, this is copied into all other “read” permissions and to a new “public” role.

  • create – “public” create permissions. Should typically not be used.

  • update – “public” update permissions. Should typically not be used.

  • delete – “public” delete permissions. Should typically not be used.

  • kwargs

    pairs of roles and permissions. E.g.:

    - ``"<role>": { "read": ["field_1", ...]}``  (leave as is)
    - ``"<role>": "*"`` (grant all permissions)
    - ``"<role>": [ "read", ("update", "id") ]`` (expand and fill
    

    defaults).

Returns

property permissions: dict

Proxy for interacting with permissions dictionary.

protect()[source]

Turns on authorization.

protected()[source]

Turns on authorization during the current context.

When the context exits, returns to the prior _protected.

>>> user = BaseUser(id="123", protected=False)
>>> user.id
'123'
>>> user.id = "456"
>>> user.id
'456'
>>> with user.protected():
...     user.id = "123"
Traceback (most recent call last):
PermissionError: ...
>>> user.id
'456'

Note

It’s recommended that you keep on authorization by default, and turn it off selectively with a context manager like exposed() or (even better) allowed().

requires_authorization(key)[source]

Checks whether an attribute key should be authorized.

Exempted attributes include:

  • Methods/attributes on this mixin (authorizable_fields, always_allowed_fields, etc.).

  • Dunder methods/attributes (i.e., __some_method__).

  • SQLAlchemy generics (_sa_class_manager, _sa_instance_manager).

Danger

You can still expose sensitive information through these unauthorized fields! This project bears no liability if you forget to be careful.

roles

@property meets @classmethod

Source: http://stackoverflow.com/a/13624858

sqlalchemy_authorize.utils module

class sqlalchemy_authorize.utils.classproperty(f)[source]

Bases: object

@property meets @classmethod

Source: http://stackoverflow.com/a/13624858

sqlalchemy_authorize.utils.is_dunder(name: str) bool[source]

Check if name is wrapped in double underscores.

>>> is_dunder("__some_dunder__")
True
>>> is_dunder("_some_not_dunder")
False
Parameters

name

Returns

Module contents

Top-level package for SQL Alchemy Authorize.