{"id":13481707,"url":"https://github.com/dbkaplan/dry-rest-permissions","last_synced_at":"2025-03-27T12:31:19.869Z","repository":{"id":48187428,"uuid":"39894746","full_name":"dbkaplan/dry-rest-permissions","owner":"dbkaplan","description":"Rules based permissions for the Django Rest Framework","archived":false,"fork":false,"pushed_at":"2022-12-26T20:02:19.000Z","size":47,"stargazers_count":373,"open_issues_count":23,"forks_count":60,"subscribers_count":18,"default_branch":"master","last_synced_at":"2024-05-12T07:40:51.664Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dbkaplan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-07-29T13:26:24.000Z","updated_at":"2024-02-10T07:03:56.000Z","dependencies_parsed_at":"2023-01-31T01:01:07.634Z","dependency_job_id":null,"html_url":"https://github.com/dbkaplan/dry-rest-permissions","commit_stats":null,"previous_names":["helioscene/dry-rest-permissions"],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dbkaplan%2Fdry-rest-permissions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dbkaplan%2Fdry-rest-permissions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dbkaplan%2Fdry-rest-permissions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dbkaplan%2Fdry-rest-permissions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dbkaplan","download_url":"https://codeload.github.com/dbkaplan/dry-rest-permissions/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221975226,"owners_count":16910340,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T17:00:54.509Z","updated_at":"2024-10-30T15:31:19.677Z","avatar_url":"https://github.com/dbkaplan.png","language":"Python","funding_links":[],"categories":["Libs","Packages"],"sub_categories":["Permissions management","Authorization"],"readme":"# dry-rest-permissions\n\n\n[\u003cimg src=\"https://api.travis-ci.org/dbkaplan/dry-rest-permissions.svg?branch=master\"\u003e](http://travis-ci.org/dbkaplan/dry-rest-permissions?branch=master) [\u003cimg src=\"https://img.shields.io/pypi/v/dry-rest-permissions.svg\"\u003e](https://pypi.python.org/pypi/dry-rest-permissions)\n\n## Overview\n\nRules based permissions for the Django Rest Framework.\n\nThis framework is a perfect fit for apps that have many tables and relationships between them. It provides a framework that allows you to define, for each action or groups of actions, what users have permission for based on existing data in your database.\n\n## What does DRY Rest Permissions provide?\n\n1. A framework for defining global and object level permissions per action.\n  1. Support for broadly defining permissions by grouping actions into safe and unsafe types.\n  2. Support for defining only global (table level) permissions or only object (row level) permissions.\n  3. Support for custom list and detail actions.\n2. A serializer field that will return permissions for an object to your client app. This is DRY and works with your existing permission definitions.\n3. A framework for limiting list requests based on permissions\n  1. Support for custom list actions\n  \n## Why is DRY Rest Permissions different than other DRF permission packages?\n\nMost other DRF permissions are based on django-guardian. Django-guardian is an explicit approach to permissions that requires data to be saved in tables that explicitly grants permissions for certain actions. For apps that have many ways for a user to be given permission to certain actions, this approach can be very hard to maintain.\n\nFor example: you may have an app which lets you create and modify projects if you are an admin of an association that owns the project. This means that a user's permission will be granted or revoked based on many possibilities including ownership of the project transferring to a different association, the user's admin status in the association changing and the user entering or leaving the association. This would need a lot of triggers that would key off of these actions and explicitly change permissions.\n\nDRY Rest Permissions allows developers to easily describe what gives someone permission using the current data in an implicit way.\n\n## Requirements\n\n-  Python (2.7+)\n-  Django (1.8, 1.10, 2.0)\n-  Django REST Framework (3.5, 3.6, 3.7)\n\n## Installation\n\nInstall using ``pip``…\n\n    $ pip install dry-rest-permissions\n\n## Setup\n\nAdd to INSTALLED_APPS\n```python\nINSTALLED_APPS = (\n    ...\n    'dry_rest_permissions',\n)\n```\n## Global vs. Object Permissions\nDRY Rest Permissions allows you to define both global and object level permissions.\n\nGlobal permissions are always checked first and define the ability of a user to take an action on an entire model. For example you can define whether a user has the ability to update any projects from the database.\n\nObject permissions are checked if global permissions pass and define whether a user has the ability to perform a specific action on a single object. These are also known as row level permissions.\nNote: list and create actions are the only standard actions that are only global. There is no such object level permission call because they are whole table actions.\n\n## Read/Write permissions vs. Specific Actions\nDRY Rest Permissions allows you to define permissions for both the standard actions (``list``, ``retrieve``, ``update``, ``destroy`` and ``create``) and custom actions defined using ``@detail_route`` and ``@list_route``.\n\nIf you don't need to define permissions on a granular action level you can generally define read or write permissions for a model. \"Read\" permissions groups together list and retrieve, while \"write\" permissions groups together destroy, update and create. All custom actions that use ``GET`` methods are considered read actions and all other methods are considered write.\n\nSpecific action permissions take precedence over general read or write permissions. For example you can lock down write permissions by always returning ``False`` and open up just update permissions for certain users.\n\nThe ``partial_update`` action is also supported, but by default is grouped with the update permission.\n\n## Add permissions to an API\n\nPermissions can be added to ModelViewSet based APIs.\n\n### Add permission class to a ModelViewSet\n\nFirst you must add ``DRYPermissions`` to the viewset's ``permission_classes``\n```python\nfrom rest_framework import viewsets\nfrom dry_rest_permissions.generics import DRYPermissions\n\n\nclass ProjectViewSet(viewsets.ModelViewSet):\n    permission_classes = (DRYPermissions,)\n    queryset = Project.objects.all()\n    serializer_class = ProjectSerializer\n```\nYou may also use ``DRYGlobalPermissions`` and ``DRYObjectPermissions``, which will only check global or object permissions.\n\nIf you want to define DRYPermissions for only some method types you can override the get_permissions function on the view like this:\n```python\ndef get_permissions(self):\n    if self.request.method == 'GET' or self.request.method == 'PUT':\n        return [DRYPermissions(),]\n    return []\n```\n\n### Define permission logic on the model\nPermissions for DRY Rest permissions are defined on the model so that they can be accessed both from the view for checking and from the serializer for display purposes with the ``DRYPermissionsField``.\n\n**Global permissions** are defined as either ``@staticmethod`` or ``@classmethod`` methods with the format ``has_\u003caction/read/write\u003e_permission``.\n\n**Object permissions** are defined as methods with the format ``has_object_\u003caction/read/write\u003e_permission``.\n\nThe following example shows how you would allow all users to read and create projects, while locking down the ability for any user to perform any other write action. In the example, read global and object permissions return ``True``, which grants permission to those actions. Write, globally returns False, which locks down write actions. However, create is a specific action and therefore takes precedence over write and gives all users the ability to create projects.\n```python\nfrom django.db import models\nfrom django.contrib.auth.models import User\n\nclass Project(models.Model):\n    owner = models.ForeignKey('User')\n    \n    @staticmethod\n    def has_read_permission(request):\n        return True\n      \n    def has_object_read_permission(self, request):\n        return True\n      \n    @staticmethod\n    def has_write_permission(request):\n        return False\n      \n    @staticmethod\n    def has_create_permission(request):\n        return True\n``` \n  Now we will add to this example and allow project owners to update or destroy a project.\n```python\nclass Project(models.Model):\n    owner = models.ForeignKey('User')\n    ...\n      \n    @staticmethod\n    def has_write_permission(request):\n        \"\"\"\n        We can remove the has_create_permission because this implicitly grants that permission.\n        \"\"\"\n        return True\n      \n    def has_object_write_permission(self, request):\n        return request.user == self.owner\n```\n  If we just wanted to grant update permission, but not destroy we could do this:\n```python\nclass Project(models.Model):\n    owner = models.ForeignKey('User')\n    ...\n  \n    @staticmethod\n    def has_write_permission(request):\n        \"\"\"\n        We can remove the has_create_permission because this implicitly grants that permission.\n        \"\"\"\n        return True\n      \n    def has_object_write_permission(self, request):\n        return False\n      \n    def has_object_update_permission(self, request):\n        return request.user == self.owner\n```\n### Custom action permissions\nIf a custom action, ``publish``, were created using ``@detail_route`` then permissions could be defined like so:\n```python\nclass Project(models.Model):\n    owner = models.ForeignKey('User')\n    ...\n      \n    @staticmethod\n    def has_publish_permission(request):\n        return True\n      \n    def has_object_publish_permission(self, request):\n        return request.user == self.owner\n``` \n### Helpful decorators\nThree decorators were defined for common permission checks\n``@allow_staff_or_superuser`` - Allows any user that has staff or superuser status to have the permission.\n``@authenticated_users`` - This permission will only be checked for authenticated users. Unauthenticated users will automatically be denied permission.\n``@unauthenticated_users`` - This permission will only be checked for unauthenticated users. Authenticated users will automatically be denied permission.\n\nExample:\n```python\nfrom dry_rest_permissions.generics import allow_staff_or_superuser, authenticated_users\n\n\nclass Project(models.Model):\n    owner = models.ForeignKey('User')\n    ...\n      \n    @staticmethod\n    @authenticated_users\n    def has_publish_permission(request):\n        return True\n      \n    @allow_staff_or_superuser\n    def has_object_publish_permission(self, request):\n        return request.user == self.owner\n```\n## Returning Permissions to the Client App\nYou often need to know all of the possible permissions that are available to the current user from within your client app so that you can show certain create, edit and destroy options. Sometimes you need to know the permissions on the client app so that you can display messages to them. ``DRYPermissionsField`` allows you to return these permissions in a serializer without having to redefine your permission logic. DRY!\n```python\nfrom dry_rest_permissions.generics import DRYPermissionsField\n\n\nclass ProjectSerializer(serializers.ModelSerializer):\n    permissions = DRYPermissionsField()\n    \n    class Meta:\n        model = Project\n        fields = ('id', 'owner', 'permissions')\n```  \nThis response object will look like this:\n```json\n{\n    \"id\": 1,\n    \"owner\": 100,\n    \"permissions\": {\n        \"read\": true,\n        \"write\": false,\n        \"create\": true,\n        \"update\": true\n    }\n}\n```\n#### Definition\n``DRYPermissionsField(actions=None, additional_actions=None, global_only=False, object_only=False, **kwargs):``\n\n``actions`` - This can be passed a list in order to limit the actions that are looked up.\n\n``additional_actions`` - If you add custom actions then you can have DRYPermissionsField look them up by adding an array of the actions as so ``permissions = DRYPermissionsField(additional_actions=['publish'])``.\n\n``global_only`` - If set to ``True`` then it will only look up global permissions.\n\n``object_only`` - If set to ``True`` then it will only look up object permissions.\n\nThis field only returns what is defined on the model. By default it retrieves all default action types that are defined.\n\nA serializer with this field MUST have the request accessible via the serializer's context. By default DRF passes the request to all serializers that is creates. However, if you create a serializer yourself you will have to add the request manually like this:\n```python\nserializer = TestSerializer(data=request.data, context={'request': request})\n```\n\n## Filtering lists by action type\nMany times it is not enough to say that a user does not have permission to view a list of items. Instead you want a user to only be able to view a partial list of items. In this case DRY Rest Permissions built on the filter concept using ``DRYPermissionFiltersBase`` to apply permissions to specific actions.\n\nIf you want to apply the same permissions to all list requests (the standard one and custom list actions) you could do the following:\n```python\nfrom django.db.models import Q\nfrom rest_framework import viewsets\nfrom dry_rest_permissions.generics import DRYPermissionFiltersBase\n\n\nclass ProjectFilterBackend(DRYPermissionFiltersBase):\n\n    def filter_list_queryset(self, request, queryset, view):\n        \"\"\"\n        Limits all list requests to only be seen by the owners or creators.\n        \"\"\"\n        return queryset.filter(Q(owner=request.user) | Q(creator=request.user))\n\n\nclass ProjectViewSet(viewsets.ModelViewSet):\n    serializer_class = ProjectSerializer\n    queryset = Project.objects.all()\n    filter_backends = (ProjectFilterBackend,)\n```\nIf you had a custom list action called ``owned`` that returned just the owned projects you could do this:\n```python\nfrom django.db.models import Q\nfrom rest_framework import viewsets\nfrom dry_rest_permissions.generics import DRYPermissionFiltersBase\n\n\nclass ProjectFilterBackend(DRYPermissionFiltersBase):\n    action_routing = True\n    \n    def filter_list_queryset(self, request, queryset, view):\n        \"\"\"\n        Limits all list requests to only be seen by the owners or creators.\n        \"\"\"\n        return queryset.filter(Q(owner=request.user) | Q(creator=request.user))\n        \n    def filter_owned_queryset(self, request, queryset, view):\n        return queryset.filter(owner=request.user)\n\n\nclass ProjectViewSet(viewsets.ModelViewSet):\n    serializer_class = ProjectSerializer\n    queryset = Project.objects.all()\n    filter_backends = (ProjectFilterBackend,)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdbkaplan%2Fdry-rest-permissions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdbkaplan%2Fdry-rest-permissions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdbkaplan%2Fdry-rest-permissions/lists"}