154 lines
5.7 KiB
Python
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)
|