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:
parent
3208836fae
commit
6b104e2bf1
2 changed files with 49 additions and 4 deletions
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue