# Access Control
With `access-control` you can manage access control list to check
wether a principal has access to a context with a certain permission.
## Concepts
### ACL (Access Control List)
An *ACL* is an ordered list of *ACE* (Access Control Entry). Every *Context*
has an *ACL*.
### ACE (Access Control Entry)
An *ACE* consists of:
- a *Permit*
- a *Principal*
- a *Permission*
### Principal
A *Principal* represents an entity, typically a user or group.
This means that a typical user can have multiple principals, like `everyone`,
`userid:1234` and `group:admin`.
### Permit
The *Permit* is either ALLOW or DENY. This means that you can specify in the
*ACE* that a *Principal* has either to be denied of allowed access to the
*Context*.
### Context
The *Context* is a resource, like a page on a website, including the context of
that resource, like the folders in which the page is located.
Every context has an *ACL*.
### Permission
The *Permission* is the action like `view`, `change name`, `create user` on the *Context*.
### Matching
To get the *Permit* for a combination of *Context*, *Principal* and *Permission*,
the *ACL* of the context will be looked up (in the specified order). When there is
a match (based on *Principal* and *Permission*), the specified *Permit* (DENY
or ALLOW) is returned. When there is no match, the first match with *ACL* of the
parent (like folders) will be returned.
When there is still no match, a DENY will be returned.
## Example
>>> import access_control as ac
>>> from typing import Optional
Create some principals, next to the predefined ac.principal.everyone
and ac.principal.authenticated.
>>> user_1 = ac.principal.Principal('user:1')
>>> group_admin = ac.principal.Principal('group:admin')
Create some context. You can use predefined ObjectContext which can make a context
from any object.
>>> class Page():
... def __init__(self, name: str, parent: Optional["Page"]):
... self.name = name
... self.parent = parent
>>> root_page = Page('root', None)
>>> contact_page = Page('contact', root_page)
>>> context_contact_page = ac.context.ObjectContext(contact_page)
>>> context_root = ac.context.ObjectContext(root_page)
Create permissions. For the contact page you can define a view and an edit permission
>>> view_permission = ac.permission.Permission('view')
>>> edit_permission = ac.permission.Permission('edit')
Next we need to glue them together in acls.
The context has a `acl` attribute which has the acl of the context *and* the parents of
the context. A subscription_list of the `subscribe` package will be used to
get the acl of a certain context. You can subscribe one or more functions to
a subscription_list of the context. All acls will be combined in the order
of the subscription_list.
Only the admins can edit the page.
>>> @context_contact_page.acl_subscription_list.subscribe()
... def get_acl(context):
... return [ac.acl.ACE(ac.permit.Permit.ALLOW, group_admin, edit_permission)]
And everyone can view everything.
>>> @context_root.acl_subscription_list.subscribe()
... def get_acl(context):
... return [ac.acl.ACE(ac.permit.Permit.ALLOW, ac.principal.everyone, view_permission)]
When a user want to access the page for edit, we can ask whether the user is allowed.
Therefor we need to know the principals of that user.
>>> unauthenticated_user_principals = [ac.principal.everyone]
>>> admin_user_princpals = {ac.principal.everyone, ac.principal.authenticated, user_1, group_admin}
Both users can access the root and contact page with view permission
>>> ac.context.get_permit(context_contact_page, admin_user_princpals, view_permission) == ac.permit.Permit.ALLOW
True
>>> ac.context.get_permit(context_root, admin_user_princpals, view_permission) == ac.permit.Permit.ALLOW
True
>>> ac.context.get_permit(context_contact_page, unauthenticated_user_principals, view_permission) == ac.permit.Permit.ALLOW
True
>>> ac.context.get_permit(context_root, unauthenticated_user_principals, view_permission) == ac.permit.Permit.ALLOW
True
The unauthenticated user has no edit permission to the contact page
>>> ac.context.get_permit(context_contact_page, unauthenticated_user_principals, edit_permission) == ac.permit.Permit.DENY
True
The admin user does have access
>>> ac.context.get_permit(context_contact_page, admin_user_princpals, edit_permission) == ac.permit.Permit.ALLOW
True