Skip to content

Commit

Permalink
WIP: Update RBAC guide for plugin writers
Browse files Browse the repository at this point in the history
Explain the concept of roles, their relationship to permissions, how
they are associated with personas and objects and also remove any
mention of django-guardian that is to be removed in 3.20.

fixes #2463

Co-authored-by: bmbouter <bmbouter@gmail.com>
  • Loading branch information
mdellweg and bmbouter committed Apr 8, 2022
1 parent 0238741 commit 0488477
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 60 deletions.
2 changes: 2 additions & 0 deletions CHANGES/plugin_api/2463.doc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Updated plugin writers RBAC guide to explain more roles and less permissions. Removed mentions of
django-guardian.
25 changes: 22 additions & 3 deletions docs/plugins/plugin-writer/concepts/rbac/access_policy.rst
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,7 @@ authorized.

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.
being assigned. Django's permission checking machinery assumes superusers bypass authorization checks.


Custom ViewSet Actions
Expand Down Expand Up @@ -235,3 +233,24 @@ different Permission check by declaring the ``permission_classes`` check. For ex
...
permission_classes = tuple()
...
.. _permission_checking_machinery:

Permission Checking Machinery
-----------------------------

drf-access-policy provides a feature to enable conditional checks to be globally available as their
docs `describe here <https://rsinger86.github.io/ drf-access-policy/reusable_conditions/>`_. Pulp
enables the ``reusable_conditions`` in its settings.py file, allowing a variety of condition
checks to be globally available. Pulp enables this as follows:

.. code-block:: python
DRF_ACCESS_POLICY = {"reusable_conditions": "pulpcore.app.global_access_conditions"}
The ``pulpcore.app.global_access_conditions`` provides the following checks that are available for
both users and plugin writers to use in their policies:

.. automodule:: pulpcore.app.global_access_conditions
:members:
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ example assigning the ``"core.task_viewer"`` role to the group ``"foo"``.
.. code-block:: python
{
"function": "add_for_groups",
"function": "add_roles_for_groups",
"parameters": {
"roles": ["core.task_viewer"],
"groups": "foo",
Expand Down
103 changes: 51 additions & 52 deletions docs/plugins/plugin-writer/concepts/rbac/permissions.rst
Original file line number Diff line number Diff line change
@@ -1,31 +1,26 @@
Permissions
===========
Permissions and Roles
=====================

The permissions system provides a way to assign permissions to specific users and groups of users.
The model driving this data is provided by ``django.contrib.auth.models.Permission``. Each
``Permission`` has a name, describing it and can be associated with one or more users or groups.
The permissions system provides a way to assign permissions as part of roles to specific users and groups of users.
The models driving this data are ``django.contrib.auth.models.Permission`` and ``pulpcore.plugin.models.role.Role``. Each
``Permission`` has a name, describing it and can be associated with one or more ``Role``.
Roles can be assigned to users or groups either on the Model-Level or Object-Level.

Two types of permissions exist: Model-Level and Object-Level.

:Model-Level: A permission that is associated with a specific model, but not an instance of that
model. This allows you to express concepts like "Hilde can modify all FileRemotes".
:Object-Level: A permission that is associated with a specific instance of a specific model. This
allows you to express concepts like "Hilde can modify FileRemote(name='foo remote').
.. _model_permissions:

Model Permissions
-----------------

.. _model_level_permissions:

Model-Level Permissions
-----------------------

``Permissions`` in Django (or should i say Pulp?) are tied to models and usually map to certain actions performed thereon.
By default, each model receives four permissions:

* The “add” permission limits the user’s ability to view the “add” form and add an object.
* The “change” permission limits a user’s ability to view the change list, view the “change” form and change an object.
* The “delete” permission limits the ability to delete an object.
* The “view” permission limits the ability to view an object.

The Model-level permissions are created automatically by Django, and receive a name like:
The Model permissions are created automatically by Django, and receive a name like:
``<app_name>.<action>_<model_name>``. For example to change file remote the permission is named
``file.change_fileremote``. You can view the Permissions on a system via the Django ORM with:
``Permission.objects.all()``. See the `Django Permissions Docs <https://docs.djangoproject.com/en/
Expand All @@ -40,34 +35,16 @@ Here's an example of the Permissions automatically created for the ``FileRemote`
* ``file.delete_fileremote``


.. _object_level_permissions:

Object-Level Permissions
------------------------

Object-level permissions are provided by `django-guardian <https://django-guardian.readthedocs.io/
en/stable/>`_ which is a dependency of Pulp and enabled by default. This extends the normal Django
calls `has_perm(perm, obj=None) <https://docs.djangoproject.com/en/2.2/ref/contrib/auth/
#django.contrib.auth.models.User.has_perm>`_ `has_perms(perm_list, obj=None <https://docs.
djangoproject.com/en/2.2/ref/contrib/auth/#django.contrib.auth.models.User.has_perms>`_ to give
meaning to the ``obj`` portion of the call which Django otherwise would ignore.

Django-guardian has great docs on what it provides for interacting with object-level permissions:

* `Assigning object permissions <https://django-guardian.readthedocs.io/en/latest/userguide/assign.html#assign-obj-perms>`_
* `Checking object permissions <https://django-guardian.readthedocs.io/en/latest/userguide/check.html#standard-way>`_
* `Removing object permissions <https://django-guardian.readthedocs.io/en/latest/userguide/remove.html>`_
* `Helpful shortcut functions <https://django-guardian.readthedocs.io/en/latest/api/guardian.shortcuts.html>`_


.. _defining_custom_permissions:

Defining Custom Permissions
---------------------------

Any model can define a custom permission, and Django will automatically make a migration to add it
Any model can define custom permissions, and Django will automatically make a migration to add it
for you. See the `Django Custom Permissions Documentation <https://docs.djangoproject.com/en/2.2/
topics/auth/customizing/#custom-permissions>`_ for more information on how to do that.
In contrast to ``AccessPolicies`` and ``creation_hooks``, permissions can only be defined by the plugin writer.
As a rule of thumb, permissions should be the atomic building blocks for roles and each action that can be performed on an object should have its own permission.


.. _custom_permission_for_repository_content_modification:
Expand All @@ -91,31 +68,53 @@ Here's an example of adding a permission like this for ``FileRepository``:
class Meta:
...
permissions = (
('modify_repo_content', 'Modify Repository Content'),
permissions = (
('modify_repo_content', 'Modify Repository Content'),
)
.. note::

It is not necessary to "namespace" this ``modify_repo_content`` permission because by including
it in the meta class of your Detail view, it will already be namespaced on the correct object.

.. _permission_checking_machinery:

Permission Checking Machinery
-----------------------------
.. _roles:

drf-access-policy provides a feature to enable conditional checks to be globalls available as their
docs `describe here <https://rsinger86.github.io/ drf-access-policy/reusable_conditions/>`_. Pulp
enables the ``reusable_conditions`` in its settings.py file, allowing a variety of condition
checks to be globally available. Pulp enables this as follows:
Roles
-----

.. code-block:: python
``Roles`` are basically sets of ``Permissions`` and in Pulp, users and groups should receive their ``Permissions`` exclusively via role assignments.
Typical roles are ``owner`` for an object with all the permissions to view modify and delete the object, or ``viewer`` limited to see the object.
To scope the reach of the permissions in a role, these role are assigned to ``Users`` or ``Groups`` either on the model-level or the object-level.

DRF_ACCESS_POLICY = {"reusable_conditions": "pulpcore.app.global_access_conditions"}
:Model-Level: A role is associated to a user or group for access to a specific model, but not an instance of that
model. This allows you to express concepts like "Hilde can administer all FileRemotes".
:Object-Level: A role is associated to a user or group for access to a specific instance of a specific model. This
allows you to express concepts like "Hilde can administer FileRemote(name='foo remote').

The ``pulpcore.app.global_access_conditions`` provides the following checks that are available for
both users and plugin writers to use in their policies:
Certain roles may contain permissions that are only ever checked on the model-level.
For example the ``creator`` role for a model that contains the models ``add`` permission.

In the case for ``FileRemote``, the typical set of roles provided by the plugin looks like:

.. code-block:: python
.. automodule:: pulpcore.app.global_access_conditions
:members:
LOCKED_ROLES = {
"file.fileremote_creator": ["file.add_fileremote"],
"file.fileremote_owner": [
"file.view_fileremote",
"file.change_fileremote",
"file.delete_fileremote",
"file.manage_roles_fileremote",
],
"file.fileremote_viewer": ["file.view_fileremote"],
}
Roles com in two flavors.
First there are so called locked roles that are provided by plugins.
Their name needs to be prefixed by the plugin ``app_label`` followed by a dot (see the example above).
They can be seen, but not modified via the api, and are kept up to date with their definition in the plugin code.
That way, plugins can ship default access policies that rely on those roles.
The other flavor are user defined roles.
These are managed and owned by the site admin via the Pulp API, and plugin code will not interfere with them.
Users can opt to use the the provided locked roles or roll their own.
4 changes: 2 additions & 2 deletions docs/plugins/plugin-writer/concepts/rbac/queryset_scoping.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ like more control over the QuerySet Scoping feature it can be added manually by
``get_queryset`` method to your ViewSet which returns the filtered QuerySet.

To look up objects by permission easily from an existing QuerySet use the ``get_objects_for_user``
provided by pulpcore or django-guardian. Here's an example where all items are displayed accessible
via either of the permission frameworks:
provided by pulpcore. Here's an example where all items are displayed accessible via either of the
permission frameworks:

.. code-block:: python
Expand Down
5 changes: 3 additions & 2 deletions docs/plugins/plugin-writer/concepts/rbac/users_groups.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ Users and Groups is always stored in the Django database. This is a requirement
:User: Provided by Django with the ``django.contrib.auth.models.User`` model.
:Group: Provided by Django with the ``django.contrib.auth.models.Group`` model.

Any role or permission can be assigned to either users, groups, or both. This includes both
Model-level and Object-level roles as well as permissions.
Any role can be assigned to either users, groups, or both. This includes both Model-level and
Object-level role assignments. Direct permission assignments are not recommended and cannot be
operated on within the Pulp-API.

0 comments on commit 0488477

Please sign in to comment.