Overview
========

As explained before capabilities permission system provide access to objects only based on a :py:class:`~caps.models.Share.Share`. This element provide: an identifier (uuid), a set of allowed actions to users (aka :py:class:`~caps.models.Capability`), and eventually an expiration date.

This figure shows how is it implemented in Django-Caps:

.. figure:: ../static/caps-models.drawio.png

    The Object is accessed through its Share. A Share is assigned to an Agent which identifies the user. The Share provides capabilities which are linked to Django's auth Permission.

Django-Caps provides views that will use the provided scheme in order to grant user access or actions.

:py:class:`~caps.models.object.Object`, :py:class:`~caps.models.Share.Share`,
:py:class:`~caps.models.capability.Capability` models are ``abstract``. When Object is subclassed in a concrete model,
a concrete Share is generated (accessible from subclass scope). The same occurs between Share and Capability.
This mechanism ensure different things:

- The Share and Capability models are associated to one object type, allowing reverse relations to be accessible (comparing for example over a solution using ContentType framework);
- This ensure clear segregation for Shares and capabilities per object type and reduce tables sizes;
- We can exploit this mechanism for eg. default Share capabilities;


Share
---------

As told, a Share provides a set of capabilities. Each capability grants two things: permission (access or action) and this permission to be share (or not).

A Share can be shared, which means that a new one will be created based one the current one. This creates a chains of parent-child Shares, ensuring control over the access. In Django-Caps, this process is called *derivation*, as a Share is *derived* from another one.

The first Share of this chain is the *root Share*. There only can be one Share for each object, which is owned by a single agent.

.. code-block::

    # Taking the example from index page
    from caps.models import Agent
    from .models import Post

    agent = Agent.objects.all()[0]
    agent_2 = Agent.objects.all()[2]

    # this raises ValueError: only one Share is allowed
    post = Post.objects.filter(Shares__isnull=False).first()
    access = Post.Share.create_root(agent, post)


The root Share will use the default capabilities as initial ones.


Capability
----------

A Capability represent a single action or access to be granted. It also can grant sharing this permission. It can in
some way be looked as a through table of a Django's ``ManyToManyField`` although it not implemented as is
(due to technical reasons).

A default capability is the one provided by default when creating a root Share. It is simply a capability without an assigned Share. Since a Capability table is created for each Object concrete sub-model, we are sure they will only target this sub-model.

When user derives a Share, eg for allowing Alice to access the object, he can provide her the ability to reshare it
using :py:attr:`~caps.models.capability.Capability.max_derive` provide the maximum amount of derivation as an absolute
value relative to root. Each time a Share is derived, the ``max_derive`` is decremented by one. This implies that:

..code-block:: python

    access = Share.objects.all().first()
    capability = access.capabilities.all().first()

    if capability.max_derive == 1:
        # this means that derived capability can't be reshared
        assert capability.derive().max_derive == 0
    elif capability.max_derive == 0:
        # this raises PermissionDenied, as capability can't be derived
        capability.derive()
    else:
        # this means that derived capability can't be shared
        assert not capability.derive(0).can_derive()

        # this means that derived capability can be reshared, as max_derive > 1
        assert capability.derive(1).can_derive()
