sqlalchemy_authorize package¶
Subpackages¶
Submodules¶
sqlalchemy_authorize.constants module¶
sqlalchemy_authorize.permissions_mixin module¶
- class sqlalchemy_authorize.permissions_mixin.BasePermissionsMixin(*args, protected=True, check_create=False, **kwargs)[source]¶
Bases:
objectBaseClass to add a field-level authorization policy to a
db.ModelE.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 onBaseUser.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
- allow(action: Union[str, List[str]], field: Optional[Union[str, List[str]]] = None) → List[str][source]¶
Allow
action(s) onfield(s).Note
Consider using
allowed()instead, to restrict additional permissions to awithstatement.- Parameters
action – The action(s) to allow.
field – Which field(s) to allow
action``(s) on, defaults to allow ``actionon all fields.
- Returns
actionas list.
- allowed(action: Union[str, List[str]], field: Optional[Union[str, List[str]]] = None)[source]¶
Allow
action(s) onfield(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 ``actionon 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()orauthorized_fields_for().
- authorize(action, key)[source]¶
Check whether the current user is allowed to perform
actiononself.model.<key>.First checks for exceptions to normal
osorules due toallow()ordeny(), otherwise passes the authorization request on tooso.
- 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
Noneif 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_fieldsthat the current user is allowed to performactionon.
- authorized_fields_for(role: str, action: str) → List[str][source]¶
The fields that an actor with
roleis allowed to performactionon.This is a subset of
authorizable_fieldsand won’t include fields that are inalways_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 ``actionon all fields.
- deny(action: Union[str, List[str]], field: Optional[Union[str, List[str]]] = None) → List[str][source]¶
Deny
action(s) onfield(s).- Parameters
action – The action(s) to deny.
field – Which field(s) to deny
action``(s) on, defaults to deny ``actionon all fields.
- Returns
actionas list.
- error(action: str)[source]¶
Returns an appropriate exception for the action.
This method expects to be overloaded. E.g.:
OsoPermissionsMixinwill 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¶
- 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 filldefaults).
- Returns
- property permissions: dict¶
Proxy for interacting with permissions dictionary.
- 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'
- requires_authorization(key)[source]¶
Checks whether an attribute
keyshould 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
sqlalchemy_authorize.utils module¶
Module contents¶
Top-level package for SQL Alchemy Authorize.