feat: Partial field value redaction

Finds and replaces Full Name and User email ID from specified DocTypes

Changes:
* Option "partial" added in user_data_fields
* If "redact_fields" aren't speciifed, "partial" mode is assumed
* If "rename" is set, the respective docs are renamed with self.anon
* "strict" if unset, is assumed to be False. In this case, a non conditional
query is used to delete data. If "strict" is True, Personal Data
Deletion Request will obey "filter_by" field value if defined else "owner" is used
This commit is contained in:
Gavin D'souza 2021-02-26 21:14:12 +05:30
parent 3208836fae
commit 6b104e2bf1
2 changed files with 49 additions and 4 deletions

View file

@ -293,8 +293,8 @@ after_migrate = ['frappe.website.doctype.website_theme.website_theme.after_migra
otp_methods = ['OTP App','Email','SMS']
user_data_fields = [
{"doctype": "Access Log", "strict": False},
{"doctype": "Activity Log", "strict": False},
{"doctype": "Access Log", "strict": True},
{"doctype": "Activity Log", "strict": True},
{"doctype": "Comment"},
{
"doctype": "Contact",
@ -354,7 +354,7 @@ user_data_fields = [
"email_signature",
],
},
{"doctype": "Version", "strict": False},
{"doctype": "Version", "strict": True},
]
global_search_doctypes = {

View file

@ -121,12 +121,58 @@ class PersonalDataDeletionRequest(Document):
for doctype in self.full_match_privacy_docs:
self.redact_full_match_data(doctype, email)
for doctype in self.partial_privacy_docs:
self.redact_partial_match_data(doctype)
frappe.rename_doc("User", email, anon, force=True, show_alert=False)
self.db_set("status", "Deleted")
def redact_partial_match_data(self, doctype):
match_fields = []
editable_text_fields = {
"Small Text",
"Text",
"Text Editor",
"Code",
"HTML Editor",
"Markdown Editor",
"Long Text",
"Data",
}
for df in frappe.get_meta(doctype["doctype"]).fields:
if df.fieldtype not in editable_text_fields:
continue
match_fields += [
f"`{df.fieldname}`= REPLACE(`{df.fieldname}`, %(name)s, 'REDACTED')",
f"`{df.fieldname}`= REPLACE(`{df.fieldname}`, %(email)s, '{self.anon}')",
]
update_predicate = f"SET {', '.join(match_fields)}"
where_predicate = "" if doctype.get("strict") else f"WHERE `{doctype.get('filter_by', 'owner')}` = %(email)s"
frappe.db.sql(
f"UPDATE `tab{doctype['doctype']}` {update_predicate} {where_predicate}",
{"name": self.full_name, "email": self.email},
debug=1
)
if doctype.get("rename"):
def new_name(email, number):
email_user, domain = email.split("@")
return f"{email_user}-{number}@{domain}"
for i, name in enumerate(
frappe.get_all(
doctype["doctype"],
filters={doctype.get("filter_by", "owner"): self.email},
pluck="name",
)
):
frappe.rename_doc(
doctype["doctype"], name, new_name(self.anon, i + 1), force=True, show_alert=False
)
def redact_full_match_data(self, ref, email):
"""Replaces the entire field value by the values set in the anonymization_value_map"""
@ -190,7 +236,6 @@ class PersonalDataDeletionRequest(Document):
)
if ref.get("rename") and doc["name"] != self.anon:
print(f'redact_doc: {ref["doctype"]} {doc["name"]} {self.anon}')
frappe.rename_doc(
ref["doctype"], doc["name"], self.anon, force=True, show_alert=False
)