# -*- coding: utf-8 -*-
from setuptools import setup

packages = \
['drf_confidential']

package_data = \
{'': ['*']}

install_requires = \
['django<3.0', 'djangorestframework>=3.11.0,<4.0.0']

setup_kwargs = {
    'name': 'drf-confidential',
    'version': '0.1.1',
    'description': 'Handle sensitive fields for django rest framework',
    'long_description': '# drf-confidential\n\n[![Build Status](https://travis-ci.org/resurrexi/drf-confidential.svg?branch=master)](https://travis-ci.org/resurrexi/drf-confidential)\n[![codecov](https://codecov.io/gh/resurrexi/drf-confidential/branch/master/graph/badge.svg)](https://codecov.io/gh/resurrexi/drf-confidential)\n\n**drf-confidential** is a package to help you control how a model\'s sensitive data is shared across your API.\n\n## Motivation\n\nImagine you have the following models declared as:\n\n```python\nfrom django.contrib.auth.models import AbstractUser\nfrom django.db import models\n\n\nclass Profile(AbstractUser):\n    email = models.EmailField(unique=True)\n    employee_profile = models.OneToOneField(\n        "Employee",\n        null=True,\n        on_delete=models.PROTECT,\n        related_name=\'login_account\'\n    )\n\n\nclass Employee(models.Model):\n    first_name = models.CharField(max_length=64)\n    last_name = models.CharField(max_length=64)\n    address_1 = models.CharField(max_length=256)\n    address_2 = models.CharField(max_length=256, blank=True)\n    country = models.CharField(max_length=64)\n    city = models.CharField(max_length=64)\n    phone_number = models.CharField(max_length=16)\n```\n\nEvery field except for `first_name` and `last_name` in the `Employee` model is considered sensitive data. This means that only the `Profile` user with the linked `employee_profile`, or a user with elevated privileges (e.g. an admin or HR staff), can access those fields.\n\n\nUnfortunately, there is no simple way to control permissions down to the field level in DRF. Enter **drf-confidential**.\n\n## drf-confidential in action\n\nLet\'s suppose there are 2 users:\n\n* *amazhong* is just a regular user without elevated privileges\n* *googe* is a staff/admin with elevated privileges\n\n### What happens when they make a GET request on the `Employee` list endpoint?\n\n<table>\n<thead>\n<tr><td colspan="2">GET /api/employees/</td></tr>\n</thead>\n<tbody>\n<tr><td>amazhong</td><td>googe</td></tr>\n<tr>\n<td>\n\n```json\n200 OK\n\n{\n    "count": 2,\n    "next": null,\n    "previous": null,\n    "results": [\n        {\n            "id": 1,\n            "first_name": "Ama",\n            "last_name": "Zhong",\n            "address_1": "440 Terry Ave N",\n            "address_2": "",\n            "country": "US",\n            "city": "Seattle",\n            "phone_number": "+12062661000"\n        },\n        {\n            "id": 2,\n            "first_name": "Goo",\n            "last_name": "Ge"\n        }\n    ]\n}\n```\n\n</td>\n<td>\n\n```json\n200 OK\n\n{\n    "count": 2,\n    "next": null,\n    "previous": null,\n    "results": [\n        {\n            "id": 1,\n            "first_name": "Ama",\n            "last_name": "Zhong",\n            "address_1": "440 Terry Ave N",\n            "address_2": "",\n            "country": "US",\n            "city": "Seattle",\n            "phone_number": "+12062661000"\n        },\n        {\n            "id": 2,\n            "first_name": "Goo",\n            "last_name": "Ge",\n            "address_1": "1600 Amphitheatre Pkwy",\n            "address_2": "",\n            "country": "US",\n            "city": "Mountain View",\n            "phone_number": "+16502530000"\n        }\n    ]\n}\n```\n\n</td>\n</tr>\n</tbody>\n</table>\n\n### What about GET requests at the detail level?\n\n<table>\n<thead>\n<tr><td colspan="2">GET /api/employees/1/</td></tr>\n</thead>\n<tbody>\n<tr><td>amazhong</td><td>googe</td></tr>\n<tr>\n<td>\n\n```json\n200 OK\n\n{\n    "id": 1,\n    "first_name": "Ama",\n    "last_name": "Zhong",\n    "address_1": "440 Terry Ave N",\n    "address_2": "",\n    "country": "US",\n    "city": "Seattle",\n    "phone_number": "+12062661000"\n}\n```\n\n</td>\n<td>\n\n```json\n200 OK\n\n{\n    "id": 1,\n    "first_name": "Ama",\n    "last_name": "Zhong",\n    "address_1": "440 Terry Ave N",\n    "address_2": "",\n    "country": "US",\n    "city": "Seattle",\n    "phone_number": "+12062661000"\n}\n```\n\n</td>\n</tr>\n</tbody>\n</table>\n\n<table>\n<thead>\n<tr><td colspan="2">GET /api/employees/2/</td></tr>\n</thead>\n<tbody>\n<tr><td>amazhong</td><td>googe</td></tr>\n<tr>\n<td>\n\n```json\n200 OK\n\n{\n    "id": 2,\n    "first_name": "Goo",\n    "last_name": "Ge"\n}\n```\n\n</td>\n<td>\n\n```json\n200 OK\n\n{\n    "id": 2,\n    "first_name": "Goo",\n    "last_name": "Ge",\n    "address_1": "1600 Amphitheatre Pkwy",\n    "address_2": "",\n    "country": "US",\n    "city": "Mountain View",\n    "phone_number": "+16502530000"\n}\n```\n\n</td>\n</tr>\n</tbody>\n</table>\n\n### What about create?\n\n<table>\n<thead>\n<tr><td colspan="2">\nPOST /api/employees/\n\n```json\n{\n    "first_name": "Ah",\n    "last_name": "Poh",\n    "address_1": "One Apple Park Way",\n    "address_2": "",\n    "country": "US",\n    "city": "Cupertino",\n    "phone_number": "+14089961010"\n}\n```\n\n</td></tr>\n</thead>\n<tbody>\n<tr><td>amazhong</td><td>googe</td></tr>\n<tr>\n<td>\n\n```json\n403 FORBIDDEN\n```\n\n</td>\n<td>\n\n```json\n201 CREATED\n\n{\n    "id": 3,\n    "first_name": "Ah",\n    "last_name": "Poh",\n    "address_1": "One Apple Park Way",\n    "address_2": "",\n    "country": "US",\n    "city": "Cupertino",\n    "phone_number": "+14089961010"\n}\n```\n\n</td>\n</tr>\n</tbody>\n</table>\n\n### And update?\n\n<table>\n<thead>\n<tr><td colspan="2">\nPATCH /api/employees/1/\n\n```json\n{\n    "address_1": "123 New Drive",\n    "phone_number": "+13214567890"\n}\n```\n\n</td></tr>\n</thead>\n<tbody>\n<tr><td>amazhong</td><td>googe</td></tr>\n<tr>\n<td>\n\n```json\n200 OK\n\n{\n    "id": 1,\n    "first_name": "Ama",\n    "last_name": "Zhong",\n    "address_1": "123 New Drive",\n    "address_2": "",\n    "country": "US",\n    "city": "Seattle",\n    "phone_number": "+13214567890"\n}\n```\n\n</td>\n<td>\n\n```json\n200 OK\n\n{\n    "id": 1,\n    "first_name": "Ama",\n    "last_name": "Zhong",\n    "address_1": "123 New Drive",\n    "address_2": "",\n    "country": "US",\n    "city": "Seattle",\n    "phone_number": "+13214567890"\n}\n```\n\n</td>\n</tr>\n</tbody>\n</table>\n\n<table>\n<thead>\n<tr><td colspan="2">\nPATCH /api/employees/2/\n\n```json\n{\n    "address_1": "123 New Drive",\n    "phone_number": "+13214567890"\n}\n```\n\n</td></tr>\n</thead>\n<tbody>\n<tr><td>amazhong</td><td>googe</td></tr>\n<tr>\n<td>\n\n```json\n403 FORBIDDEN\n```\n\n</td>\n<td>\n\n```json\n200 OK\n\n{\n    "id": 2,\n    "first_name": "Goo",\n    "last_name": "Ge",\n    "address_1": "123 New Drive",\n    "address_2": "",\n    "country": "US",\n    "city": "Mountain View",\n    "phone_number": "+13214567890"\n}\n```\n\n</td>\n</tr>\n</tbody>\n</table>\n\n### And delete?\n\n<table>\n<thead>\n<tr><td colspan="2">DELETE /api/employees/1/</td></tr>\n</thead>\n<tbody>\n<tr><td>amazhong</td><td>googe</td></tr>\n<tr>\n<td>\n\n```json\n204 NO CONTENT\n```\n\n</td>\n<td>\n\n```json\n204 NO CONTENT\n```\n\n</td>\n</tr>\n</tbody>\n</table>\n\n<table>\n<thead>\n<tr><td colspan="2">DELETE /api/employees/2/</td></tr>\n</thead>\n<tbody>\n<tr><td>amazhong</td><td>googe</td></tr>\n<tr>\n<td>\n\n```json\n403 FORBIDDEN\n```\n\n</td>\n<td>\n\n```json\n204 NO CONTENT\n```\n\n</td>\n</tr>\n</tbody>\n</table>\n\n## Basic usage\n\n### Step 1\n\nCreate a confidential permission on your model and `python manage.py migrate`.\n\n```python\nclass Employee(models.Model):\n    ...  # fields defined earlier above\n\n    class Meta:\n        permissions = (\n            ("view_sensitive_employee", "Can view employees\' sensitive data"),\n        )\n```\n\n### Step 2\n\nAdd the `ConfidentialFieldsMixin` to your serializer and define your `confidential_fields` and `user_relation` lookup.\n\n```python\nfrom rest_framework import serializers\n\nfrom drf_confidential.mixins import ConfidentialFieldsMixin\n\n\nclass EmployeeSerializer(ConfidentialFieldsMixin, serializers.ModelSerializer):\n    class Meta:\n        model = Employee\n        fields = "__all__"\n        confidential_fields = (\n            "address_1",\n            "address_2",\n            "country",\n            "city",\n            "phone_number",\n        )\n        user_relation = "login_account"\n```\n\n`ConfidentialFieldsMixin` is configured to look for cases where either the request user is the model instance, the request user owns the model instance, the request user has a relation to the model instance, or the request user has the elevated permissions. The `confidential_fields` meta attribute specifies which fields are considered sensitive. The `user_relation` lookup specifies the relation of the model to the user model. In the [model definitions above](#Motivation), the relation to the `Profile` model from the `Employee` model is through the back-reference, `login_account`.\n\n### Step 3\n\nAdd the `ConfidentialFieldsPermission` as a permission class to the viewset.\n\n```python\nfrom rest_framework.viewsets import ModelViewSet\n\nfrom drf_confidential.permissions import ConfidentialFieldsPermission\n\n\nclass EmployeeViewSet(ModelViewSet):\n    serializer_class = EmployeeSerializer\n    queryset = Employee.objects.all()\n    permission_classes = [\n        ...  # your default permissions, e.g. IsAuthenticated\n        ConfidentialFieldsPermission\n    ]\n```\n\nThe permission follows the logic that a user must have either elevated permissions, have ownership, or have a relation to the model instance if they want to `update`, `partial_update`, or `delete`. For `create`, only users with elevated permissions are allowed. For `retrieve` and `list`, all users are allowed.\n',
    'author': 'Liquan Yang',
    'author_email': 'yang.liquan87@gmail.com',
    'maintainer': None,
    'maintainer_email': None,
    'url': 'https://github.com/resurrexi/drf-confidential',
    'packages': packages,
    'package_data': package_data,
    'install_requires': install_requires,
    'python_requires': '>=3.7,<4.0',
}


setup(**setup_kwargs)
