Skip to main content

Django REST Framework package for role-based field masking in serializers

Project description

django-role-based-masking

PyPI version Python versions Django versions License: MIT

A Django REST Framework package that masks serializer output fields based on the requesting user's role using configurable masking strategies.

Features

  • 🎭 Role-based field masking - Automatically mask sensitive fields based on user roles
  • 🔧 Flexible strategies - Built-in masking strategies (full, partial, email, percentage)
  • 🪆 Nested support - Mask fields in nested serializers using dotted paths
  • 🎨 Custom strategies - Easy to add custom masking logic
  • DRF integration - Simple mixin for existing serializers
  • 🧪 Well tested - Comprehensive test suite
  • 📦 Production ready - Supports Python 3.10+, Django 3.2+, DRF 3.14+

Installation

pip install django-role-based-masking

Quick Start

1. Basic Usage

from rest_framework import serializers
from django_role_based_masking.serializers import RoleMaskedModelSerializer

class EmployeeSerializer(RoleMaskedModelSerializer):
    class Meta:
        model = Employee
        fields = ['id', 'name', 'email', 'phone', 'salary', 'ssn']

        # Define masking rules per role
        mask_fields = {
            "ADMIN": {},  # No masking for admins
            "MANAGER": {
                "ssn": "partial_last:4",
                "salary": "percentage:50",
            },
            "EMPLOYEE": {
                "email": "email",
                "phone": "partial_last:4",
                "salary": "full",
                "ssn": "full",
            },
            "ANONYMOUS": {
                "email": "full",
                "phone": "full",
                "salary": "full",
                "ssn": "full",
            }
        }

2. Example Output

For a user with role MANAGER:

# Original data
{
    "id": 1,
    "name": "John Doe",
    "email": "john.doe@example.com",
    "phone": "+1234567890",
    "salary": "75000",
    "ssn": "123-45-6789"
}

# Masked output
{
    "id": 1,
    "name": "John Doe",
    "email": "john.doe@example.com",
    "phone": "+1234567890",
    "salary": "*****0",  # 50% masked
    "ssn": "***-**-6789"  # Only last 4 visible
}

Built-in Masking Strategies

full

Masks the entire string.

"salary": "full"
# "75000" -> "*****"

partial_last:N

Keeps the last N characters, masks the rest.

"ssn": "partial_last:4"
# "123-45-6789" -> "***-**-6789"

"phone": "partial_last:4"
# "+1234567890" -> "*******7890"

email

Masks the email local-part except the first character.

"email": "email"
# "john.doe@example.com" -> "j*******@example.com"

percentage:N

Masks N% of the string length from the left.

"salary": "percentage:70"
# "75000" -> "****0"  # 70% of 5 chars = 3.5 -> 4 chars masked

noop

No masking (passthrough).

"name": "noop"
# "John Doe" -> "John Doe"

Nested Field Masking

Use dotted paths to mask fields in nested serializers:

class UserProfileSerializer(RoleMaskedModelSerializer):
    address = AddressSerializer()

    class Meta:
        model = UserProfile
        fields = ['name', 'email', 'address']

        mask_fields = {
            "GUEST": {
                "email": "email",
                "address.street": "full",
                "address.postal_code": "partial_last:3",
            }
        }

Custom Masking Strategies

Using a Callable

def custom_mask(value, mask_char="*"):
    """Mask middle portion of string."""
    if len(value) <= 4:
        return mask_char * len(value)
    keep = len(value) // 4
    return value[:keep] + mask_char * (len(value) - 2 * keep) + value[-keep:]

class MySerializer(RoleMaskedModelSerializer):
    class Meta:
        model = MyModel
        fields = ['id', 'secret_code']

        mask_fields = {
            "USER": {
                "secret_code": custom_mask,
            }
        }

Using a Dictionary

mask_fields = {
    "USER": {
        "phone": {
            "name": "partial_last",
            "kwargs": {"keep_last": 2}
        }
    }
}

Configuration

Add these settings to your Django settings.py:

# Default role attribute path (supports dotted paths like "profile.role")
DRM_ROLE_ATTR = "role"  # default

# Default masking character
DRM_MASK_CHAR = "*"  # default

# Default strategy when not specified
DRM_DEFAULT_STRATEGY = "full"  # default

Role Resolution

The package resolves user roles using the following priority:

  1. Exact role match - Uses the role-specific rules
  2. DEFAULT role - Falls back to "DEFAULT" if defined
  3. ANONYMOUS role - Falls back to "ANONYMOUS" if user is not authenticated
  4. No masking - If no rules match, returns data unchanged

Role Attribute Path

By default, the package looks for user.role. You can customize this:

# settings.py
DRM_ROLE_ATTR = "profile.user_role"  # Supports dotted paths

Anonymous users automatically get the "ANONYMOUS" role.

Advanced Usage

Using the Mixin

from rest_framework import serializers
from django_role_based_masking.serializers import RoleMaskedSerializerMixin

class CustomSerializer(RoleMaskedSerializerMixin, serializers.Serializer):
    name = serializers.CharField()
    email = serializers.EmailField()

    # Define mask_fields as class attribute
    mask_fields = {
        "GUEST": {
            "email": "email"
        }
    }

Programmatic Masking

from django_role_based_masking.masking import apply_masking

data = {
    "name": "John Doe",
    "email": "john@example.com",
    "salary": "75000"
}

rules = {
    "EMPLOYEE": {
        "salary": "full"
    }
}

context = {"request": request}  # DRF request object

masked_data = apply_masking(data, rules, context)

Requirements

  • Python 3.10+
  • Django 3.2+
  • Django REST Framework 3.14+

Development

Setup

# Clone the repository
git clone https://github.com/gaikwadakshay79/django-role-based-masking.git
cd django-role-based-masking

# Create virtual environment
python -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install dependencies
pip install -e ".[dev]"

Running Tests

pytest

Code Quality

ruff check .
ruff format .

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Changelog

See CHANGELOG.md for a list of changes.

Support

Acknowledgments

Built with ❤️ for the Django and DRF community.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_role_based_masking-1.0.0.tar.gz (13.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_role_based_masking-1.0.0-py3-none-any.whl (11.9 kB view details)

Uploaded Python 3

File details

Details for the file django_role_based_masking-1.0.0.tar.gz.

File metadata

File hashes

Hashes for django_role_based_masking-1.0.0.tar.gz
Algorithm Hash digest
SHA256 5455d53f7e109d1a6aa9b822753ba159f0724221f0121cd178ecb74f86811c14
MD5 fd4a65965f9fd441213b58fb4e9199bc
BLAKE2b-256 857ed01aed4bdee8621a7c769114966a5d3b56cfe225b400c86d0e347c4c54ed

See more details on using hashes here.

File details

Details for the file django_role_based_masking-1.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for django_role_based_masking-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b305d89376e46b5ab2edd40a14b0d39accd6eae60098b791ba962e74ef0387d5
MD5 f130ac882f9f9a2231c9506cf8380c15
BLAKE2b-256 951bb170918706f31864e19dc854012bed07578424fc4c3f48fdae75605e3af2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page