154 lines
5.7 KiB
Python

"""ACL authorization policy.
This module introduces ``AbstractACLAutzPolicy`` - an abstract base class to
create ACL authorization policy class. The subclass should define how to
retrieve user's groups.
"""
import abc
from ...acl.acl import extend_user_groups, get_groups_permitted
from ..abc import AbstractAutzPolicy
class AbstractACLAutzPolicy(AbstractAutzPolicy):
"""Abstract base class for ACL authorization policy.
As the library does not know how to get groups for user and it is always
up to application, it provides abstract authorization ACL policy
class. Subclass should implement ``acl_groups`` method to use it with
``autz_middleware``.
Note that an ACL context can be specified globally while initializing
policy or locally through ``autz.permit`` function's parameter. A local
context will always override a global one while checking permissions.
If there is no local context and global context is not set then the
``permit`` method will raise a ``RuntimeError``.
A context is a sequence of ACL tuples which consist of an
``Allow``/``Deny`` action, a group, and a set of permissions for that ACL
group. For example:
.. code-block:: python
context = [(Permission.Allow, 'view_group', {'view', }),
(Permission.Allow, 'edit_group', {'view', 'edit'}),]
ACL tuple sequences are checked in order, with the first tuple that
matches the group the user is a member of, and includes the permission
passed to the function, to be the matching ACL group. If no ACL group is
found, the ``permit`` method returns ``False``.
Groups and permissions need only be immutable objects, so can be strings,
numbers, enumerations, or other immutable objects.
.. note:: Groups that are returned by ``acl_groups`` (if they are not
``None``) will then be extended internally with ``Group.Everyone`` and
``Group.AuthenticatedUser``.
Usage example:
.. code-block:: python
from aiohttp import web
from aiohttp_auth import autz
from aiohttp_auth.autz import autz_required
from aiohttp_auth.autz.policy import acl
from aiohttp_auth.permissions import Permission
class ACLAutzPolicy(acl.AbstractACLAutzPolicy):
def __init__(self, users, context=None):
super().__init__(context)
# we will retrieve groups using some kind of users dict
# here you can use db or cache or any other needed data
self.users = users
async def acl_groups(self, user_identity):
# implement application specific logic here
user = self.users.get(user_identity, None)
if user is None:
return None
return user['groups']
def init(loop):
app = web.Application(loop=loop)
...
# here you need to initialize aiohttp_auth.auth middleware
...
users = ...
# Create application global context.
# It can be overridden in autz.permit fucntion or in
# autz_required decorator using local context explicitly.
context = [(Permission.Allow, 'view_group', {'view', }),
(Permission.Allow, 'edit_group', {'view', 'edit'})]
autz.setup(app, ACLAutzPolicy(users, context))
# authorization using autz decorator applying to app request handler
@autz_required('view')
async def handler_view(request):
# authorization using permit
if await autz.permit(request, 'edit'):
pass
"""
def __init__(self, context=None):
"""Initialize ACL authorization policy.
Args:
context: global ACL context, default to ``None``. Should be a list
of ACL rules.
"""
self.context = context
@abc.abstractmethod
async def acl_groups(self, user_identity):
"""Return ACL groups for given user identity.
Subclass should implement this method to return a sequence of
groups for given ``user_identity``.
Args:
user_identity: User identity returned by ``auth.get_auth``.
Returns:
Sequence of ACL groups for the user identity (could be empty to
give a chance to ``Group.Everyone`` and
``Group.AuthenticatedUser``) or ``None`` (``permit`` will always
return ``False``).
"""
pass # pragma: no cover
async def permit(self, user_identity, permission, context=None):
"""Check if user is allowed for given permission with given context.
Args:
user_identity: Identity of the user returned by
``aiohttp_auth.auth.get_auth`` function
permission: The specific permission requested.
context: A context provided for checking permissions. Could be
optional if a global context is specified through policy
initialization.
Returns:
``True`` if permission is allowed, ``False`` otherwise.
Raises:
RuntimeError: If there is neither global context nor local one.
"""
if context is None:
# try global context
context = self.context
if context is None:
raise RuntimeError('Context should be specified globally through '
'acl autz policy or passed as a parameter of '
'permit function or autz_required decorator.')
groups = extend_user_groups(user_identity,
await self.acl_groups(user_identity))
return get_groups_permitted(groups, permission, context)