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 there.
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_or_domain_perms:file.add_fileremote",
},
{
"action": ["retrieve"],
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_or_domain_or_obj_perms:file.view_fileremote",
},
{
"action": ["update", "partial_update", "set_label", "unset_label"],
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_or_domain_or_obj_perms:file.change_fileremote",
},
{
"action": ["destroy"],
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_or_domain_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 toonly the objects that user can view
. See queryset scoping.create
is allowed by any authenticated user with thefile.add_fileremote
permission.retrieve
(the detail view of an object) is allowed by an authenticated user who has thefile.view_fileremote
permission. Although users are allowed to perform an operation what they can list will still be restricted toonly the objects that user can view
. See queryset scoping.update
orpartial_update
is allowed by an authenticated user who has thefile.change_fileremote
permission.destroy
is allowed by any authenticated user with thefile.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 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.
Note
If you are making your plugin compatible with Domains,
use the has_model_or_domain_perms
and has_model_or_domain_or_obj_perms
checks where appropriate.
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.
Django's permission checking machinery assumes superusers 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_domain_or_obj_perms:file.modify_repo_content",
"has_remote_param_model_or_domain_or_obj_perms:file.view_fileremote",
]
}
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
.
This attribute should contain all of statements
and creation_hooks
.
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:
class FileRemoteViewSet(RemoteViewSet):
# <...>
DEFAULT_ACCESS_POLICY = {
"statements": [
{
"action": ["list"],
"principal": "authenticated",
"effect": "allow",
},
{
"action": ["create"],
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_or_domain_perms:file.add_fileremote",
},
{
"action": ["retrieve"],
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_or_domain_or_obj_perms:file.view_fileremote",
},
{
"action": ["update", "partial_update", "set_label", "unset_label"],
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_or_domain_or_obj_perms:file.change_fileremote",
},
{
"action": ["destroy"],
"principal": "authenticated",
"effect": "allow",
"condition": "has_model_or_domain_or_obj_perms:file.delete_fileremote",
},
],
"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"
],
"file.fileremote_viewer": ["file.view_fileremote"],
}
<...>
For an explanation of the creation_hooks
see the 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.
Allow Granting Permissions by the Object Owners¶
To allow object owners to grant access to other users, first add a manage_roles
permission to the model.
class FileRemote(Remote):
<...>
class Meta:
permissions = [
("manage_roles_fileremote", "Can manage roles on file remotes"),
]
Now include the RolesMixin
in the definition of the viewset and add statements for its verbs.
class FileRemoteViewSet(RemoteViewSet, RolesMixin):
<...>
DEFAULT_ACCESS_POLICY = {
"statements": [
<...>
{
"action": ["list_roles", "add_role", "remove_role"],
"principal": "authenticated",
"effect": "allow",
"condition": ["has_model_or_domain_or_obj_perms:file.manage_roles_fileremote"],
},
]
}
LOCKED_ROLES = {
"file.fileremote_owner": [
<...>
<...>
}
Handling Objects created prior to RBAC¶
Prior to RBAC being enabled, admin
was the only user.
They have is_superuser=True
which generally causes them to pass any permission check even without explicit permissions being assigned.
Viewset Enforcement¶
Pulp configures the DEFAULT_PERMISSION_CLASSES
in the settings file to use pulpcore.plugin.access_policy.AccessPolicyFromDB
by default.
This ensures that by defining a DEFAULT_ACCESS_POLICY
on your Viewset, Pulp will automatically save it to the database at migration-time,
and your Viewset will be protected without additional effort.
!!! Note:
This default configuration is supposed to change to `AccessPolicyFromSettings` with Pulp 4.
Any not explicitely configured access policy will still be taken from the default.
This strategy allows users to completely customize or disable the DRF Permission checks Pulp uses like any typical DRF project would.
Also like a typical DRF project, individual Viewsets or views can also be customized to use a different Permission check by declaring the permission_classes
check.
For example, here is the StatusView
which disables permission checks entirely as follows:
class StatusView(APIView):
...
permission_classes = tuple()
...
Permission Checking Machinery¶
drf-access-policy
provides a feature to enable conditional checks to be globally available.
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:
DRF_ACCESS_POLICY = {"reusable_conditions": ["pulpcore.app.global_access_conditions"]}
The pulpcore.app.global_access_conditions provides several checks that are available for both users and plugin writers to use in their policies.
Custom Permission Checks¶
Plugins can provide their own permission checks by defining them in a app.global_access_conditions
module and adding a statement like
DRF_ACCESS_POLICY = {
"dynaconf_merge_unique": True,
"reusable_conditions": ["pulp_container.app.global_access_conditions"],
}
to their app.settings
module.
Reference¶
pulpcore.app.global_access_conditions
¶
has_model_perms(request, view, action, permission)
¶
Checks if the current user has a model-level permission.
This is usable as a conditional check in an AccessPolicy. Here is an example checking for "file.add_fileremote" permission at the model-level.
::
{
...
"condition": "has_model_perms:file.add_fileremote",
}
Parameters:
-
request
(Request
) –The request being made.
-
view
(subclass rest_framework.viewsets.GenericViewSet
) –The view being checked for authorization.
-
action
(str
) –The action being performed, e.g. "destroy".
-
permission
(str
) –The name of the Permission to be checked. In the form
app_label.codename
, e.g. "core.delete_task".
Returns:
-
–
True if the user has the Permission named by the
permission
argument at the model-level. False otherwise.
has_domain_perms(request, view, action, permission)
¶
Checks if the user has current domain-level permission.
If DOMAIN_ENABLED, use the incoming request's domain for the permission check, else return False.
has_obj_perms(request, view, action, permission)
¶
Checks if the current user has object-level permission on the specific object.
The object in this case is the one the action is operating on, e.g. the URL
/pulp/api/v3/tasks/15939b47-6b6d-4613-a441-939ca4ba6e63/
is operating on the Task object
with pk=15939b47-6b6d-4613-a441-939ca4ba6e63
.
This is usable as a conditional check in an AccessPolicy. Here is an example checking for the "file.view_fileremote" permissions at the object-level.
::
{
...
"condition": "has_obj_perms:file.view_fileremote",
}
Parameters:
-
request
(Request
) –The request being made.
-
view
(subclass rest_framework.viewsets.GenericViewSet
) –The view being checked for authorization.
-
action
(str
) –The action being performed, e.g. "destroy".
-
permission
(str
) –The name of the Permission to be checked. In the form
app_label.codename
, e.g. "core.delete_task".
Returns:
-
–
True if the user has the Permission named by the
permission
argument on the object being operated on at the object-level. False otherwise.
has_model_or_domain_perms(request, view, action, permission)
¶
Checks if the current user has either model-level (global) or domain-level permissions.
has_model_or_domain_or_obj_perms(request, view, action, permission)
¶
Checks if the current user has the permission across all levels of Pulp.
This checks all three levels of permissions in Pulp: model, domain, and object level. Returns True if the user has the permission on any of those levels, False otherwise.
has_model_or_obj_perms(request, view, action, permission)
¶
Checks if the current user has either model-level or object-level permissions.
The object in this case is the one the action is operating on, e.g. the URL
/pulp/api/v3/tasks/15939b47-6b6d-4613-a441-939ca4ba6e63/
is operating on the Task object
with pk=15939b47-6b6d-4613-a441-939ca4ba6e63
.
This is usable as a conditional check in an AccessPolicy. Here is an example checking for "file.view_fileremote" permission at either the model-level or object-level.
::
{
...
"condition": "has_model_or_obj_perms:file.view_fileremote",
}
Parameters:
-
request
(Request
) –The request being made.
-
view
(subclass rest_framework.viewsets.GenericViewSet
) –The view being checked for authorization.
-
action
(str
) –The action being performed, e.g. "destroy".
-
permission
(str
) –The name of the Permission to be checked. In the form
app_label.codename
, e.g. "core.delete_task".
Returns:
-
–
True if the user has the Permission named by the
permission
argument at the model-level or on the object being operated on at the object-level. False otherwise.
has_remote_param_obj_perms(request, view, action, permission)
¶
Checks if the current user has object-level permission on the remote
object.
The object in this case is the one specified by the remote
parameter. For example when
syncing the remote
parameter is passed in as an argument.
This is usable as a conditional check in an AccessPolicy. Here is an example checking for the "file.view_fileremote" permissions at the object-level.
::
{
...
"condition": "has_remote_param_obj_perms:file.view_fileremote",
}
Since it is checking a remote
object the permission argument should be one of the following:
- "file.change_fileremote" - Permission to change the
FileRemote
. - "file.view_fileremote" - Permission to view the
FileRemote
. - "file.delete_fileremote" - Permission to delete the
FileRemote
.
Parameters:
-
request
(Request
) –The request being made.
-
view
(subclass rest_framework.viewsets.GenericViewSet
) –The view being checked for authorization.
-
action
(str
) –The action being performed, e.g. "destroy".
-
permission
(str
) –The name of the Permission to be checked. In the form
app_label.codename
, e.g. "core.delete_task".
Returns:
-
–
True if the user has the Permission named by the
permission
argument on theremote
parameter at the object-level or if there is no remote. False otherwise.
has_remote_param_model_or_domain_or_obj_perms(request, view, action, permission)
¶
Checks if the current user has the permission on the remote
param.
has_remote_param_model_or_obj_perms(request, view, action, permission)
¶
Checks if the current user has either model-level or object-level permissions on the remote
.
The object in this case is the one specified by the remote
parameter. For example when
syncing the remote
parameter is passed in as an argument.
This is usable as a conditional check in an AccessPolicy. Here is an example checking for "file.view_fileremote" permission at either the model-level or object-level.
::
{
...
"condition": "has_remote_param_model_or_obj_perms:file.view_fileremote",
}
Since it is checking a remote
object the permission argument should be one of the following:
- "file.change_fileremote" - Permission to change the
FileRemote
. - "file.view_fileremote" - Permission to view the
FileRemote
. - "file.delete_fileremote" - Permission to delete the
FileRemote
.
Parameters:
-
request
(Request
) –The request being made.
-
view
(subclass rest_framework.viewsets.GenericViewSet
) –The view being checked for authorization.
-
action
(str
) –The action being performed, e.g. "destroy".
-
permission
(str
) –The name of the Permission to be checked. In the form
app_label.codename
, e.g. "core.delete_task".
Returns:
-
–
True if the user has the Permission named by the
permission
at the model-level or on the argument on theremote
parameter at the object-level. False otherwise.
has_repo_attr_obj_perms(request, view, action, permission)
¶
Checks if the current user has object-level permission on a repository
attribute.
The object in this case is the one specified by the repository
attribute of a resource
which is being operated on. For example, when deleting a repository version, a repository
is its attribute.
This is usable as a conditional check in an AccessPolicy. Here is an example checking for the "file.view_filerepository" permissions at the object-level.
::
{
...
"condition": "has_repo_attr_obj_perms:file.view_filerepository",
}
Since it is checking a repository
object the permission argument should be one of the
following:
- "file.change_filerepository" - Permission to change the
FileRepository
. - "file.view_filerepository" - Permission to view the
FileRepository
. - "file.delete_filerepository" - Permission to delete the
FileRepository
. - any custom permission a plugin has defined for their repository.
Parameters:
-
request
(Request
) –The request being made.
-
view
(subclass rest_framework.viewsets.GenericViewSet
) –The view being checked for authorization.
-
action
(str
) –The action being performed, e.g. "destroy".
-
permission
(str
) –The name of the repository Permission to be checked. In the form
app_label.codename
, e.g. "file.delete_filerepository".
Returns:
-
–
True if the user has the Permission named by the
permission
argument on the -
–
repository
attribute at the object-level. False otherwise.
has_repo_attr_model_or_domain_or_obj_perms(request, view, action, permission)
¶
Checks if the current user has the permission on the repository
attribute.
has_repo_attr_model_or_obj_perms(request, view, action, permission)
¶
Checks if the current user has model-level or object-level permissions on a repository
.
The object in this case is the one specified by the repository
attribute of a resource
which is being operated on. For example, when deleting a repository version, a repository
is its attribute.
This is usable as a conditional check in an AccessPolicy. Here is an example checking for the "file.view_filerepository" permissions at the object-level.
::
{
...
"condition": "has_repo_attr_model_or_obj_perms:file.view_filerepository",
}
Since it is checking a repository
object the permission argument should be one of the
following:
- "file.change_filerepository" - Permission to change the
FileRepository
. - "file.view_filerepository" - Permission to view the
FileRepository
. - "file.delete_filerepository" - Permission to delete the
FileRepository
. - any custom permission a plugin has defined for their repository.
Parameters:
-
request
(Request
) –The request being made.
-
view
(subclass rest_framework.viewsets.GenericViewSet
) –The view being checked for authorization.
-
action
(str
) –The action being performed, e.g. "destroy".
-
permission
(str
) –The name of the repository Permission to be checked. In the form
app_label.codename
, e.g. "file.delete_filerepository".
Returns:
-
–
True if the user has the Permission on the
repository
attribute named by the -
–
permission
at the model or object level. False otherwise.
has_repository_obj_perms(request, view, action, permission)
¶
Checks whether a user has the requested object permission on the repository in the URL.
This check is meant to be used for relations nested beneath a repository endpoint, e.g. the list of repository versions belonging to that repository. It will fail for other endpoints.
::
{
...
"condition": "has_repository_obj_perms:file.filerepository_delete",
},
Parameters:
-
request
(Request
) –The request being made.
-
view
(subclass rest_framework.viewsets.GenericViewSet
) –The view being checked for authorization.
-
action
(str
) –The action being performed, e.g. "destroy".
-
permission
(str
) –The name of the Repository Permission to be checked. In the form
app_label.codename
, e.g. "file.filerepository_change".
Returns:
-
–
True if the user has the Permission on the
Repository
specified in the URL named by the -
–
permission
at object level. False otherwise.
has_repository_model_or_domain_or_obj_perms(request, view, action, permission)
¶
Checks if the current user has the permission for the repository in the URL.
has_repository_model_or_obj_perms(request, view, action, permission)
¶
Checks whether a user has the requested model or object permission on the repository in the URL.
This check is meant to be used for relations nested beneath a repository endpoint, e.g. the list of repository versions belonging to that repository. It will fail for other endpoints.
::
{
...
"condition": "has_repository_model_or_obj_perms:file.filerepository_delete",
},
Parameters:
-
request
(Request
) –The request being made.
-
view
(subclass rest_framework.viewsets.GenericViewSet
) –The view being checked for authorization.
-
action
(str
) –The action being performed, e.g. "destroy".
-
permission
(str
) –The name of the Repository Permission to be checked. In the form
app_label.codename
, e.g. "file.filerepository_change".
Returns:
-
–
True if the user has the Permission on the
Repository
specified in the URL named by the -
–
permission
at model or object level. False otherwise.
has_repo_or_repo_ver_param_model_or_domain_or_obj_perms(request, view, action, permission)
¶
Checks if the current user has permission on the repository
or repository_version
.
has_repo_or_repo_ver_param_model_or_obj_perms(request, view, action, permission)
¶
Checks if the current user has object-level permission on the repository
object.
The object in this case is the one specified by the repository
or repository_version
parameter. For example when publishing the repository
parameter is passed in as an argument.
This is usable as a conditional check in an AccessPolicy. Here is an example checking for the "file.view_filerepository" permissions at the object-level.
::
{
...
"condition": "has_repo_or_repo_ver_param_model_or_obj_perms:file.view_filerepository",
}
Since it is checking a repository
object the permission argument should be one of the
following:
- "file.change_filerepository" - Permission to change the
FileRepository
. - "file.view_filerepository" - Permission to view the
FileRepository
. - "file.delete_filerepository" - Permission to delete the
FileRepository
. - "file.sync_filerepository" - Permission to sync the
FileRepository
.
Parameters:
-
request
(Request
) –The request being made.
-
view
(subclass rest_framework.viewsets.GenericViewSet
) –The view being checked for authorization.
-
action
(str
) –The action being performed, e.g. "destroy".
-
permission
(str
) –The name of the Permission to be checked. In the form
app_label.codename
, e.g. "core.delete_task".
Returns:
-
–
True if the user has the Permission named by the
permission
argument on therepository
orrepository_version
parameter at the object-level or if there is no repository. False otherwise.
has_required_repo_perms_on_upload(request, view, action, permission)
¶
Checks if the current user has permission to upload content to the repository
object.
Since content queryset scoping prevents users from seeing orphaned content by default this
also checks to make sure that any user that isn't an admin has also supplied the repository
parameter when performing a content upload.
In addition, if the user has specified labels with this content, make sure they are allowed to do so by looking for the "core.manage_labels" permission if the action is "create".
This is usable as a conditional check in an AccessPolicy for content uploads. Here is an example checking for the "file.modify_filerepository" permission.
::
{
...
"condition": "has_required_repo_perms_on_upload:file.modify_filerepository",
}
Parameters:
-
request
(Request
) –The request being made.
-
view
(subclass rest_framework.viewsets.GenericViewSet
) –The view being checked for authorization.
-
action
(str
) –The action being performed, e.g. "destroy".
-
permission
(str
) –The name of the Permission to be checked. In the form
app_label.codename
, e.g. "core.delete_task".
Returns:
-
–
True if the user has supplied the
repository
parameter and has the Permission on it named by thepermission
argument, or is an admin. False otherwise.
has_publication_param_model_or_domain_or_obj_perms(request, view, action, permission)
¶
Checks if the current user has permission on the Publication
param.
has_publication_param_model_or_obj_perms(request, view, action, permission)
¶
Checks if the current user has permission on the Publication
object.
The object in this case is the one specified by the publication
parameter. For example when
distributing the publication
parameter is passed in as an argument.
This is usable as a conditional check in an AccessPolicy. Here is an example checking for the "file.view_filepublication" permission.
::
{
...
"condition": "has_publication_param_model_or_obj_perms:file.view_filepublication",
}
Since it is checking a Publication
object the permission argument should be one of the
following:
- "file.view_filepublication" - Permission to view the
FilePublication
. - "file.delete_filepublication" - Permission to delete the
FilePublication
.
Parameters:
-
request
(Request
) –The request being made.
-
view
(subclass rest_framework.viewsets.GenericViewSet
) –The view being checked for authorization.
-
action
(str
) –The action being performed, e.g. "destroy".
-
permission
(str
) –The name of the Permission to be checked. In the form
app_label.codename
, e.g. "core.delete_task".
Returns:
-
–
True if the user has the Permission named by the
permission
argument on thepublication
parameter or if there is no publication. False otherwise.
has_upload_param_model_or_domain_or_obj_perms(request, view, action, permission)
¶
Checks if the current user has permission on the Upload
param.
has_upload_param_model_or_obj_perms(request, view, action, permission)
¶
Checks if the current user has permission on the Upload
object.
The object in this case is the one specified by the upload
parameter, for example to a
one-shot content creation call.
This is usable as a conditional check in an AccessPolicy. Here is an example checking for the "core.change_upload" permissions.
::
{
...
"condition": "has_upload_param_model_or_obj_perms:core.change_upload",
}
Since it is checking a Upload
object the permission argument should be one of the following:
- "core.view_upload" - Permission to view the
Upload
. - "core.change_upload" - Permission to change the
Upload
. - "core.delete_upload" - Permission to delete the
Upload
.
Parameters:
-
request
(Request
) –The request being made.
-
view
(subclass rest_framework.viewsets.GenericViewSet
) –The view being checked for authorization.
-
action
(str
) –The action being performed, e.g. "destroy".
-
permission
(str
) –The name of the Permission to be checked. In the form
app_label.codename
, e.g. "core.delete_task".
Returns:
-
–
True if the user has the Permission named by the
permission
argument on theUpload
parameter or if there is no upload. False otherwise.
has_group_obj_perms(request, view, action, permission)
¶
Checks whether a user has the requested object permission on the Group in the URL.
This check is meant to be used for relations nested beneath the group endpoint, e.g. the list of users to belong to that group. It will fail for other endpoints.
::
{
...
"condition": "has_group_obj_perms:core.group_delete",
},
Parameters:
-
request
(Request
) –The request being made.
-
view
(subclass rest_framework.viewsets.GenericViewSet
) –The view being checked for authorization.
-
action
(str
) –The action being performed, e.g. "destroy".
-
permission
(str
) –The name of the Group Permission to be checked. In the form
app_label.codename
, e.g. "core.group_change".
Returns:
-
–
True if the user has the Permission on the
Group
specified in the URL named by the -
–
permission
at object level. False otherwise.
has_group_model_or_obj_perms(request, view, action, permission)
¶
Checks whether a user has the requested object or model permission on the Group in the URL.
This check is meant to be used for relations nested beneath the group endpoint, e.g. the list of users to belong to that group. It will fail for other endpoints.
::
{
...
"condition": "has_group_model_or_obj_perms:core.group_delete",
},
Parameters:
-
request
(Request
) –The request being made.
-
view
(subclass rest_framework.viewsets.GenericViewSet
) –The view being checked for authorization.
-
action
(str
) –The action being performed, e.g. "destroy".
-
permission
(str
) –The name of the Group Permission to be checked. In the form
app_label.codename
, e.g. "core.group_change".
Returns:
-
–
True if the user has the Permission on the
Group
specified in the URL named by the -
–
permission
at the model or object level. False otherwise.