mirror of
https://github.com/jazzband/django-auditlog.git
synced 2026-03-16 22:20:26 +00:00
Add mask_fields argument in register (#310)
Co-authored-by: Hasan Ramezani <hasan.r67@gmail.com>
This commit is contained in:
parent
dad4fb893b
commit
bb5f99533e
6 changed files with 68 additions and 1 deletions
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#### Improvements
|
||||
- feat: enable use of replica database (delegating the choice to `DATABASES_ROUTER`) ([#359](https://github.com/jazzband/django-auditlog/pull/359))
|
||||
- Add `mask_fields` argument in `register` to mask sensitive information when logging ([#3710](https://github.com/jazzband/django-auditlog/pull/310))
|
||||
|
||||
#### Important notes
|
||||
- LogEntry no longer save to same database instance is using
|
||||
|
|
|
|||
|
|
@ -76,6 +76,19 @@ def get_field_value(obj, field):
|
|||
return value
|
||||
|
||||
|
||||
def mask_str(value: str) -> str:
|
||||
"""
|
||||
Masks the first half of the input string to remove sensitive data.
|
||||
|
||||
:param value: The value to mask.
|
||||
:type value: str
|
||||
:return: The masked version of the string.
|
||||
:rtype: str
|
||||
"""
|
||||
mask_limit = int(len(value) / 2)
|
||||
return "*" * mask_limit + value[mask_limit:]
|
||||
|
||||
|
||||
def model_instance_diff(old, new, fields_to_check=None):
|
||||
"""
|
||||
Calculates the differences between two model instances. One of the instances may be ``None`` (i.e., a newly
|
||||
|
|
@ -145,7 +158,13 @@ def model_instance_diff(old, new, fields_to_check=None):
|
|||
new_value = get_field_value(new, field)
|
||||
|
||||
if old_value != new_value:
|
||||
diff[field.name] = (smart_str(old_value), smart_str(new_value))
|
||||
if model_fields and field.name in model_fields["mask_fields"]:
|
||||
diff[field.name] = (
|
||||
mask_str(smart_str(old_value)),
|
||||
mask_str(smart_str(new_value)),
|
||||
)
|
||||
else:
|
||||
diff[field.name] = (smart_str(old_value), smart_str(new_value))
|
||||
|
||||
if len(diff) == 0:
|
||||
diff = None
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ class AuditlogModelRegistry:
|
|||
include_fields: Optional[List[str]] = None,
|
||||
exclude_fields: Optional[List[str]] = None,
|
||||
mapping_fields: Optional[Dict[str, str]] = None,
|
||||
mask_fields: Optional[List[str]] = None,
|
||||
):
|
||||
"""
|
||||
Register a model with auditlog. Auditlog will then track mutations on this model's instances.
|
||||
|
|
@ -48,6 +49,7 @@ class AuditlogModelRegistry:
|
|||
:param include_fields: The fields to include. Implicitly excludes all other fields.
|
||||
:param exclude_fields: The fields to exclude. Overrides the fields to include.
|
||||
:param mapping_fields: Mapping from field names to strings in diff.
|
||||
:param mask_fields: The fields to mask for sensitive info.
|
||||
|
||||
"""
|
||||
|
||||
|
|
@ -57,6 +59,8 @@ class AuditlogModelRegistry:
|
|||
exclude_fields = []
|
||||
if mapping_fields is None:
|
||||
mapping_fields = {}
|
||||
if mask_fields is None:
|
||||
mask_fields = []
|
||||
|
||||
def registrar(cls):
|
||||
"""Register models for a given class."""
|
||||
|
|
@ -67,6 +71,7 @@ class AuditlogModelRegistry:
|
|||
"include_fields": include_fields,
|
||||
"exclude_fields": exclude_fields,
|
||||
"mapping_fields": mapping_fields,
|
||||
"mask_fields": mask_fields,
|
||||
}
|
||||
self._connect_signals(cls)
|
||||
|
||||
|
|
@ -114,6 +119,7 @@ class AuditlogModelRegistry:
|
|||
"include_fields": list(self._registry[model]["include_fields"]),
|
||||
"exclude_fields": list(self._registry[model]["exclude_fields"]),
|
||||
"mapping_fields": dict(self._registry[model]["mapping_fields"]),
|
||||
"mask_fields": list(self._registry[model]["mask_fields"]),
|
||||
}
|
||||
|
||||
def _connect_signals(self, model):
|
||||
|
|
|
|||
|
|
@ -115,6 +115,18 @@ class SimpleMappingModel(models.Model):
|
|||
history = AuditlogHistoryField()
|
||||
|
||||
|
||||
@auditlog.register(mask_fields=["address"])
|
||||
class SimpleMaskedModel(models.Model):
|
||||
"""
|
||||
A simple model used for register's mask_fields kwarg
|
||||
"""
|
||||
|
||||
address = models.CharField(max_length=100)
|
||||
text = models.TextField()
|
||||
|
||||
history = AuditlogHistoryField()
|
||||
|
||||
|
||||
class AdditionalDataIncludedModel(models.Model):
|
||||
"""
|
||||
A model where get_additional_data is defined which allows for logging extra
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ from auditlog_tests.models import (
|
|||
SimpleExcludeModel,
|
||||
SimpleIncludeModel,
|
||||
SimpleMappingModel,
|
||||
SimpleMaskedModel,
|
||||
SimpleModel,
|
||||
UUIDPrimaryKeyModel,
|
||||
)
|
||||
|
|
@ -413,6 +414,19 @@ class SimpleMappingModelTest(TestCase):
|
|||
)
|
||||
|
||||
|
||||
class SimpeMaskedFieldsModelTest(TestCase):
|
||||
"""Log masked changes for fields in mask_fields"""
|
||||
|
||||
def test_register_mask_fields(self):
|
||||
smm = SimpleMaskedModel(address="Sensitive data", text="Looong text")
|
||||
smm.save()
|
||||
self.assertEqual(
|
||||
smm.history.latest().changes_dict["address"][1],
|
||||
"*******ve data",
|
||||
msg="The diff function masks 'address' field.",
|
||||
)
|
||||
|
||||
|
||||
class AdditionalDataModelTest(TestCase):
|
||||
"""Log additional data if get_additional_data is defined in the model"""
|
||||
|
||||
|
|
|
|||
|
|
@ -76,6 +76,21 @@ during the `register()` call.
|
|||
|
||||
You do not need to map all the fields of the model, any fields not mapped will fall back on their ``verbose_name``. Django provides a default ``verbose_name`` which is a "munged camel case version" so ``product_name`` would become ``Product Name`` by default.
|
||||
|
||||
**Masking fields**
|
||||
|
||||
Fields that contain sensitive info and we want keep track of field change but not to contain the exact change.
|
||||
|
||||
To mask specific fields from the log you can pass ``mask_fields`` to the ``register``
|
||||
method. If ``mask_fields`` is specified, the first half value of the fields is masked using ``*``.
|
||||
|
||||
For example, to mask the field ``address``, use::
|
||||
|
||||
auditlog.register(MyModel, mask_fields=['address'])
|
||||
|
||||
.. versionadded:: 1.1.0
|
||||
|
||||
Masking fields
|
||||
|
||||
Actors
|
||||
------
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue