Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Roles #1627

Merged
merged 2 commits into from
Nov 18, 2021
Merged

Roles #1627

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES/9411.doc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Adjusted the RBAC documentation for the roles framework.
7 changes: 7 additions & 0 deletions CHANGES/9411.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Added a role model to support RBAC and the utility functions ``assign_role`` and ``remove_role``.

The field ``permissions_assignment`` of access policies has been renamed to ``creation_hooks``. A
compatibility patch has been added to be removed with pulpcore=3.20.

The ``permissions`` argument to ``creation_hooks`` has been deprecated to be removed with
pulpcore=3.20.
1 change: 1 addition & 0 deletions CHANGES/9413.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added views to assign model and object level roles to users and groups.
1 change: 1 addition & 0 deletions CHANGES/9415.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Rewrote existing access policies on viewsets to use roles.
7 changes: 7 additions & 0 deletions CHANGES/plugin_api/9411.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Added ``get_objects_for_user`` to support queryset filtering by roles.
Added hooks in ``AutoAddObjPermsMixin`` to support auto-assignment of roles.

Changed the lookup for creation hooks so hooks need to be registered in
``REGISTERED_CREATION_HOOKS`` on the model to be used. The signature for creation hooks that are
registered must match the exploded version of the dict parameters from the access policy.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exploded? not sure what is going kaboom here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like unpacked. The parameters dict is unpacked (**parameters) and takes more space now. Like an explosion.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💥

Unregistered creation hooks are deprecated and support will be dropped in pulpcore 3.20.
34 changes: 23 additions & 11 deletions docs/plugins/plugin-writer/concepts/rbac/access_policy.rst
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ All access policies are stored in the database in the `pulpcore.plugin.models.Ac
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
:members: viewset_name, statements, creation_hooks

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
Expand All @@ -131,9 +131,10 @@ Shipping a Default Access Policy
--------------------------------

To ship a default access policy, define a dictionary named ``DEFAULT_ACCESS_POLICY`` as a class
attribute on a subclass of ``NamedModelViewSet`` containing both ``statements`` and
``permissions_assignment``. The ``AccessPolicy`` instance will be then be created in the
``pulp_migrate`` signal handler.
attribute on a subclass of ``NamedModelViewSet`` containing all of ``statements`` and
``creation_hooks``. The ``AccessPolicy`` instance will then be created in the ``pulp_migrate``
signal handler. In the same way you might want to specify a ``LOCKED_ROLES`` dictionary that will
define roles as lists of permissions to be used in the access policy.

Here's an example of code to define a default policy:

Expand Down Expand Up @@ -175,21 +176,32 @@ Here's an example of code to define a default policy:
},
],

"permissions_assignment": [
"creation_hooks": [
{
"function": "add_for_object_creator",
"parameters": None,
"permissions": [
"file.view_fileremote", "file.change_fileremote", "file.delete_fileremote"
]
"function": "add_roles_for_object_creator",
"parameters": {
"roles": "file.fileremote_owner",
},
},
],
}
LOCKED_ROLES = {
"file.fileremote_owner": [
"file.view_fileremote", "file.change_fileremote", "file.delete_fileremote"
],
"file.fileremote_viewer": ["file.view_fileremote"],
}
<...>

For an explanation of the ``permissions_assignment`` see the
For an explanation of the ``creation_hooks`` see the
:ref:`shipping_a_default_new_object_policy` documentation.

The attribute ``LOCKED_ROLES`` contains roles that are managed by the plugin author. Their name
needs to be prefixed by the plugins ``app_label`` with a dot to prevent collisions. Roles defined
there will be replicated and updated in the database after every migration. They are also
marked ``locked=True`` to prevent being modified by users. The primary purpose of these roles is to
allow plugin writers to refer to them in the default access policy.


.. _handling_objects_created_prior_to_RBAC:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,54 +17,57 @@ are run.
Defining New Object Permission Behaviors
----------------------------------------

The ``AccessPolicy.permissions_assignment`` attribute defines a set of callables that are intended
to be run when new objects are created. These do not run automatically; your models should use the
The ``AccessPolicy.creation_hooks`` attribute defines a set of callables that are intended to be
run when new objects are created. These do not run automatically; your models should use the
``pulpcore.plugin.models.AutoAddObjPermsMixin`` on the model as described in the
:ref:`enabling_new_object_permission_creation` section.

The ``AccessPolicy.permissions_assignment`` attribute is optional because not all AccessPolicy
objects create objects. If no objects are created by an endpoint, there does not need to be a
``permissions_assignment`` attribute.
The ``AccessPolicy.creation_hooks`` attribute is optional because not all AccessPolicy objects
create objects. If no objects are created by an endpoint, there does not need to be a
``creation_hooks`` attribute.

The most common auto-assignment of permissions is to the creator of an object themselves. Here is an
example assigning the ``["pulpcore.view_task", "pulpcore.change_task", "pulpcore.delete_task"]``
permissions to the creator of an object:
Permissions are associated to users via roles.

The most common auto-assignment of roles is to the creator of an object themselves. Here is an
example assigning the ``"core.task_owner"`` role to the creator of an object:

.. code-block:: python

{
"function": "add_for_object_creator",
"parameters": null,
"permissions": ["pulpcore.view_task", "pulpcore.change_task", "pulpcore.delete_task"]
}
"function": "add_roles_for_object_creator",
"parameters": {"roles": ["core.task_owner"]},
}

Another common auto-assignment of permissions is to assign to one or more users explicitly. Here is
an example assigning the ``["pulpcore.view_task", "pulpcore.change_task", "pulpcore.delete_task"]``
permissions to the users ``["alice", "bob"]``.
Another common auto-assignment of roles is to assign to one or more users explicitly. Here is an
example assigning the ``"core.task_owner"`` role to the users ``["alice", "bob"]``.

.. code-block:: python

{
"function": "add_for_users",
"parameters": ["alice", "bob"],
"permissions": ["pulpcore.view_task", "pulpcore.change_task", "pulpcore.delete_task"]
"function": "add_roles_for_users",
"parameters": {
"roles": "core.task_owner",
"users": ["alice", "bob"],
},
}

A third common auto-assignment of permissions is to assign to one or more groups explicitly. Here is
an example assigning the ``"pulpcore.view_task"`` permission to the group ``"foo"``.
A third common auto-assignment of roles is to assign to one or more groups explicitly. Here is an
example assigning the ``"core.task_viewer"`` role to the group ``"foo"``.

.. code-block:: python

{
"function": "add_for_groups",
"parameters": "foo",
"permissions": "pulpcore.view_task"
"parameters": {
"roles": ["core.task_viewer"],
"groups": "foo",
},
}

.. note::

Both the ``add_for_users`` and ``add_for_groups`` accept either a single item or list of items
for both the ``parameters`` and ``permissions`` attributes.
All the hooks shipped with pulpcore accept either a single item or list of items for their
arguments like ``roles``, ``users`` or ``groups``.


.. _enabling_new_object_permission_creation:
Expand Down Expand Up @@ -92,27 +95,26 @@ See the docstring below for more information on this mixin.
Shipping a Default New Object Policy
------------------------------------

In general, the default recommended is to use the ``add_for_object_creator`` to assign the view,
change, and delete permissions for the object created. Here is an example of a default policy like
this:
In general, the default recommended is to use the ``add_roles_for_object_creator`` to assign the
view, change, and delete permissions for the object created. Here is an example of a default policy
like this:

.. code-block:: python

FILE_REMOTE_PERMISSIONS_ASSIGNMENT = [
{
"function": "add_for_object_creator",
"parameters": None,
"permissions": [
"file.change_fileremote", "file.change_fileremote", "file.delete_fileremote"
]
}
]

AccessPolicy.objects.create(
viewset_name="remotes/file/file",
statements=FILE_REMOTE_STATEMENTS,
permissions_assignment=FILE_REMOTE_PERMISSIONS_ASSIGNMENT
)
DEFAULT_ACCESS_POLICY = {
"statements": <...>
"creation_hooks": [
{
"function": "add_roles_for_object_creator",
"parameters": {"roles": "file.fileremote_owner"},
}
],
}
LOCKED_ROLES = {
"file.fileremote_owner": [
"file.view_fileremote", "file.change_fileremote", "file.delete_fileremote"
],
}

This effectively creates a "user isolation" policy which aligns with the examples from
:ref:`shipping_default_access_policy`.
Expand All @@ -123,37 +125,53 @@ This effectively creates a "user isolation" policy which aligns with the example
Defining Custom New Object Permission Callables
-----------------------------------------------

Plugin writers can use more than the built-in callables such as ``add_for_object_creator`` or
``add_for_users`` by defining additional methods on the model itself. The callables defined in the
``function`` are method names on the Model with the following signature:
Plugin writers can use more than the built-in callables such as ``add_roles_for_object_creator`` or
``add_roles_for_users`` by defining additional methods on the model itself. The callables defined in
the ``function`` are method names on the Model that need to be registered with
``REGISTERED_CREATION_HOOKS``:

.. code-block:: python

class MyModel(BaseModel, AutoAddObjPermsMixin):

def my_custom_callable(self, permissions, parameters):
# NOTE: permissions and parameters can be either a single entity or a list of entities
from guardian.shortcuts import assign_perm
user_or_group = parameters
for permission in permissions:
assign_perm(permissions, user_or_group, self) # self is the object being assigned
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.REGISTERED_CREATION_HOOKS["my_custom_callable"] = self.my_custom_callable
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to assign this as an attribute to the class?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how we can make it extendable on subclassing as a ClassVar, without writing a complicated meta class.


def my_custom_callable(self, role, users, groups):
from pulpcore.app.util import assign_role
for user in users:
assign_role(role, user, self) # self is the object being assigned
for group in groups:
assign_role(role, group, self) # self is the object being assigned

This would be callable with a configuration like this one:

.. code-block:: python

{
"function": "my_custom_callable",
"parameters": "asdf",
"permissions": "pulpcore.view_task"
"parameters": {
"role": "pulpcore.task_viewer",
"users": ["bob"],
"groups": [],
},
}

.. note::

The ``parameters`` dict must actually match the creation hooks signature.


.. _auto_removing_permissions_on_object_deletion:

Auto Removing Permissions On Object Deletion
--------------------------------------------

.. note::

This applies to the deprecated guardian framework. Role associations are automatically deleted.

A mixin is provided for use on your models to automatically delete all object-level permissions when
an object is deleted. This is provided by the ``pulpcore.plugin.models.AutoDeleteObjPermsMixin``
mixin.
Expand Down
11 changes: 6 additions & 5 deletions docs/plugins/plugin-writer/concepts/rbac/overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,13 +42,14 @@ To add authorization for a given resource, e.g. ``FileRemote``, you'll need to:

1. Define the default ``statements`` of the new Access Policy for the resource. See the
:ref:`defining_access_policy` documentation for more information on that.
2. Define the default permissions created for new objects using the ``permissions_assignment``
2. Define the ``roles`` as sets of permissions for that resource.
3. Define the default role associations created for new objects using the ``creation_hooks``
attribute of the new Access Policy for the resource. See the
:ref:`adding_automatic_permissions_for_new_objects` documentation for more information on that.
3. Ship that Access Policy as the class attribute ``DEFAULT_ACCESS_POLICY`` of a
``NamedModelViewSet``. This will contain both the ``statements`` and ``permissions_assignment``
attributes. See the :ref:`shipping_default_access_policy` documentation for more information on
this.
4. Ship that Access Policy as the class attribute ``DEFAULT_ACCESS_POLICY`` of a
``NamedModelViewSet``. This will contain the ``statements`` and ``creation_hooks`` attributes.
Ship the roles as the ``LOCKED_ROLES`` attribute accordingly. See the
:ref:`shipping_default_access_policy` documentation for more information on this.

**Enforce the Policy:**

Expand Down
10 changes: 5 additions & 5 deletions docs/plugins/plugin-writer/concepts/rbac/queryset_scoping.rst
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,17 @@ If your ViewSet does not inherit from ``pulpcore.plugin.viewsets.NamedModelViewS
like more control over the QuerySet Scoping feature it can be added manually by adding a
``get_queryset`` method to your ViewSet which returns the filtered QuerySet.

To look up objects by permission easily from an existing QuerySet use the ``klass`` argument to
the ``get_objects_for_user`` provided by django-guardian. The ``klass`` argument may be a Model,
Manager or QuerySet object. Here's an example:
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:

.. code-block:: python

from guardian.shortcuts import get_objects_for_user
from pulpcore.plugin.util import get_objects_for_user

class MyViewSet(rest_framework.viewsets.GenericViewSet):

def get_queryset(self):
qs = super().get_queryset()
permission_name = "my.example_permission"
return get_objects_for_user(self.request.user, permission_name, klass=qs)
return get_objects_for_user(self.request.user, permission_name, qs=qs)
8 changes: 5 additions & 3 deletions docs/plugins/plugin-writer/concepts/rbac/users_groups.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ Users and Groups
================

Users and Groups is always stored in the Django database. This is a requirement so that
``Permissions`` can relate to them.
``Roles`` or ``Permissions`` can relate to them.

: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 permission can be assigned to either users, groups, or both. This includes both Model-level and
Object-level permissions.
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.


.. _viewing_users_and_groups_via_UI:
Expand All @@ -32,6 +32,8 @@ membership data.
Model-level Permissions via a UI
--------------------------------

.. note:: This only applies to permissions.

The django-admin site also provides views into the Permissions that Users and Groups have.
Additionally you can add and remove Permissions here as well.

Expand Down