.. _defining_access_policy:
Defining an Access Policy
=========================
The Access Policy controls the authorization of a given request and is enforced at the
viewset-level. Access policies are based on the AccessPolicy from `drf-access-policy
`_ which uses `policy statements
described here `_.
Example Policy
--------------
Below is an example policy used by ``FileRemote``, with an explanation of its effect below that::
[
{
"action": ["list"],
"principal": "authenticated",
"effect": "allow",
},
{
"action": ["create"],
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_perms:file.add_fileremote",
},
{
"action": ["retrieve"],
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_or_obj_perms:file.view_fileremote",
},
{
"action": ["update", "partial_update"],
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_or_obj_perms:file.change_fileremote",
},
{
"action": ["destroy"],
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_or_obj_perms:file.delete_fileremote",
},
]
The above policy allows the following four cases, and denies all others by default. Overall this
creates a "user isolation policy" whereby users with the ``file.add_fileremote`` permission can
create ``FileRemote`` objects, and users can only read/modify/delete ``FileRemote`` objects they
created.
Here's a written explanation of the policy statements:
* ``list`` is allowed by any authenticated user. Although users are allowed to perform an operation
what they can list will still be restricted to :ref:`only the objects that user can view
`.
* ``create`` is allowed by any authenticated user with the ``file.add_fileremote`` permission.
* ``retrieve`` (the detail view of an object) is allowed by an authenticated user who has the
``file.view_fileremote`` permission. Although users are allowed to perform an operation what they
can list will still be restricted to :ref:`only the objects that user can view
`.
* ``update`` or ``partial_update`` is allowed by an authenticated user who has the
``file.change_fileremote`` permission.
* ``destroy`` is allowed by any authenticated user with the ``file.delete_fileremote`` permission.
These names correspond with the `default DRF viewset action names
`_.
Authorization Conditions
------------------------
Each policy statement can contain `drf-access-policy conditions `_ which is useful for verifying a user has one or
more permissions. Pulp ships many built-in checks. See the :ref:`permission_checking_machinery`
documentation for more information on available checks.
When multiple conditions are present, **all** of them must return True for the request to be
authorized.
.. warning::
The ``admin`` user created on installations prior to RBAC being enabled has
``is_superuser=True``. Django assumes a superuser has any model-level permission even without it
being assigned. Additionally, django-guardian when checking object-level permissions defaults to
assuming the same although it is configurable. Generally, superusers are expected to bypass
authorization checks.
Custom ViewSet Actions
----------------------
The ``action`` part of a policy statement can reference `any custom action your viewset has
`_. For
example ``FileRepositoryViewSet`` has a ``sync`` custom action used by users to sync a given
``FileRepository``. Below is an example of the default policy used to guard that action::
{
"action": ["sync"],
"principal": "authenticated",
"effect": "allow",
"condition": [
"has_model_or_obj_perms:file.modify_repo_content",
"has_remote_param_model_or_obj_perms:file.view_fileremote",
]
}
.. _storing_access_policy_in_db:
Storing an Access Policy in the DB
----------------------------------
All access policies are stored in the database in the `pulpcore.plugin.models.AccessPolicy` model,
which stores the policy statements described above. Here is a look at the ``AccessPolicy`` model:
.. autoclass:: pulpcore.plugin.models.AccessPolicy
:members: viewset_name, statements, permissions_assignment
By storing these in the database they are readable to users with a GET to
``/pulp/api/v3/access_policies/``. Additionally users can PUT/PATCH modify them at
``/pulp/api/v3/access_policies/:uuid/``. Users cannot modify create or delete an Access Policy in
the database because only plugin writers create them and their viewset code expects a specific
AccessPolicy instance to exist.
.. _shipping_default_access_policy:
Shipping a Default Access Policy
--------------------------------
To ship an access policy, write a data migration that creates an ``AccessPolicy`` instance. Here's
an example of code to create an instance, which would be contained in a data migration.
.. code-block:: python
from pulpcore.plugin.models import AccessPolicy
FILE_REMOTE_STATEMENTS = [
{
"action": ["list"],
"principal": "authenticated",
"effect": "allow",
},
{
"action": ["create"],
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_perms:file.add_fileremote",
},
{
"action": ["retrieve"],
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_or_obj_perms:file.view_fileremote",
},
{
"action": ["update", "partial_update"],
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_or_obj_perms:file.change_fileremote",
},
{
"action": ["destroy"],
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_or_obj_perms:file.delete_fileremote",
},
]
FILE_REMOTE_PERMISSIONS_ASSIGNMENT = [
{
"function": "add_for_object_creator",
"parameters": None,
"permissions": [
"file.view_fileremote", "file.change_fileremote", "file.delete_fileremote"
]
}
]
AccessPolicy.objects.create(
viewset_name="FileRemoteViewSet",
statements=FILE_REMOTE_STATEMENTS,
permissions_assignment=FILE_REMOTE_PERMISSIONS_ASSIGNMENT
)
The actual ``AccessPolicy`` statement is created at the end. The other data structures store the
default policy. For en explanation of the ``permissions_assignment`` see the
:ref:`shipping_a_default_new_object_policy` documentation.
.. _handling_objects_created_prior_to_RBAC:
Handling Objects created prior to RBAC
--------------------------------------
Prior to RBAC being enabled, ``admin`` was the only user and they have ``is_superuser=True`` which
generally causes them to pass any permission check even without explicit permissions being assigned.
.. _viewset_enforcement:
Viewset Enforcement
-------------------
Protecting a viewset with your saved AccessPolicy is done by declaring a ``permission_classes``
class attribute on your ViewSet that points to ``pulpcore.plugin.access_policy.AccessPolicyFromDB``.
For example, here is the FileRemoteViewSet which enables authorization enforcement as follows:
.. code-block:: python
class FileRemoteViewSet(NamedModelViewSet):
...
permission_classes = (FileRemoteAccessPolicy,)
...