Package: drf-confidential
drf-confidentialdrf-confidentialis a package to help you control how a model's sensitive data is shared across your API.Installationpipinstalldrf-confidentialMotivationImagine you have the following models declared as:fromdjango.contrib.auth.modelsimportAbstractUserfromdjango.dbimportmodelsclassProfile(AbstractUser):email=models.EmailField(unique=True)employee_profile=models.OneToOneField("Employee",null=True,on_delete=models.PROTECT,related_name='login_account')classEmployee(models.Model):first_name=models.CharField(max_length=64)last_name=models.CharField(max_length=64)address_1=models.CharField(max_length=256)address_2=models.CharField(max_length=256,blank=True)country=models.CharField(max_length=64)city=models.CharField(max_length=64)phone_number=models.CharField(max_length=16)Every field except forfirst_nameandlast_namein theEmployeemodel is considered sensitive data. This means that only theProfileuser with the linkedemployee_profile, or a user with elevated privileges (e.g. an admin or HR staff), can access those fields.Unfortunately, there is no simple way to control permissions down to the field level in DRF. Enterdrf-confidential.drf-confidential in actionLet's suppose there are 2 users:amazhongis just a regular user without elevated privilegesgoogeis a staff/admin with elevated privilegesWhat happens when they make a GET request on theEmployeelist endpoint?GET /api/employees/amazhonggooge200OK{"count":2,"next":null,"previous":null,"results":[{"id":1,"first_name":"Ama","last_name":"Zhong","address_1":"440 Terry Ave N","address_2":"","country":"US","city":"Seattle","phone_number":"+12062661000"},{"id":2,"first_name":"Goo","last_name":"Ge"}]}200OK{"count":2,"next":null,"previous":null,"results":[{"id":1,"first_name":"Ama","last_name":"Zhong","address_1":"440 Terry Ave N","address_2":"","country":"US","city":"Seattle","phone_number":"+12062661000"},{"id":2,"first_name":"Goo","last_name":"Ge","address_1":"1600 Amphitheatre Pkwy","address_2":"","country":"US","city":"Mountain View","phone_number":"+16502530000"}]}What about GET requests at the detail level?GET /api/employees/1/amazhonggooge200OK{"id":1,"first_name":"Ama","last_name":"Zhong","address_1":"440 Terry Ave N","address_2":"","country":"US","city":"Seattle","phone_number":"+12062661000"}200OK{"id":1,"first_name":"Ama","last_name":"Zhong","address_1":"440 Terry Ave N","address_2":"","country":"US","city":"Seattle","phone_number":"+12062661000"}GET /api/employees/2/amazhonggooge200OK{"id":2,"first_name":"Goo","last_name":"Ge"}200OK{"id":2,"first_name":"Goo","last_name":"Ge","address_1":"1600 Amphitheatre Pkwy","address_2":"","country":"US","city":"Mountain View","phone_number":"+16502530000"}What about create?POST /api/employees/{"first_name":"Ah","last_name":"Poh","address_1":"One Apple Park Way","address_2":"","country":"US","city":"Cupertino","phone_number":"+14089961010"}amazhonggooge403FORBIDDEN201CREATED{"id":3,"first_name":"Ah","last_name":"Poh","address_1":"One Apple Park Way","address_2":"","country":"US","city":"Cupertino","phone_number":"+14089961010"}And update?PATCH /api/employees/1/{"address_1":"123 New Drive","phone_number":"+13214567890"}amazhonggooge200OK{"id":1,"first_name":"Ama","last_name":"Zhong","address_1":"123 New Drive","address_2":"","country":"US","city":"Seattle","phone_number":"+13214567890"}200OK{"id":1,"first_name":"Ama","last_name":"Zhong","address_1":"123 New Drive","address_2":"","country":"US","city":"Seattle","phone_number":"+13214567890"}PATCH /api/employees/2/{"address_1":"123 New Drive","phone_number":"+13214567890"}amazhonggooge403FORBIDDEN200OK{"id":2,"first_name":"Goo","last_name":"Ge","address_1":"123 New Drive","address_2":"","country":"US","city":"Mountain View","phone_number":"+13214567890"}And delete?DELETE /api/employees/1/amazhonggooge204NOCONTENT204NOCONTENTDELETE /api/employees/2/amazhonggooge403FORBIDDEN204NOCONTENTBasic usageStep 1Create a confidential permission on your model andpython manage.py migrate.classEmployee(models.Model):...# fields defined earlier aboveclassMeta:permissions=(("view_sensitive_employee","Can view employees' sensitive data"),)Step 2Add theConfidentialFieldsMixinto your serializer and define yourconfidential_fieldsanduser_relationlookup.fromrest_frameworkimportserializersfromdrf_confidential.mixinsimportConfidentialFieldsMixinclassEmployeeSerializer(ConfidentialFieldsMixin,serializers.ModelSerializer):classMeta:model=Employeefields="__all__"confidential_fields=("address_1","address_2","country","city","phone_number",)user_relation="login_account"ConfidentialFieldsMixinis 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. Theconfidential_fieldsmeta attribute specifies which fields are considered sensitive. Theuser_relationlookup specifies the relation of the model to the user model. In themodel definitions above, the relation to theProfilemodel from theEmployeemodel is through the back-reference,login_account.Step 3Add theConfidentialFieldsPermissionas a permission class to the viewset.fromrest_framework.viewsetsimportModelViewSetfromdrf_confidential.permissionsimportConfidentialFieldsPermissionclassEmployeeViewSet(ModelViewSet):serializer_class=EmployeeSerializerqueryset=Employee.objects.all()permission_classes=[...# your default permissions, e.g. IsAuthenticatedConfidentialFieldsPermission]The 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 toupdate,partial_update, ordelete. Forcreate, only users with elevated permissions are allowed. Forretrieveandlist, all users are allowed.