diff --git a/frappe/desk/form/meta.py b/frappe/desk/form/meta.py index b6ca3f31d5..fc65fc49b3 100644 --- a/frappe/desk/form/meta.py +++ b/frappe/desk/form/meta.py @@ -52,24 +52,12 @@ def get_meta(doctype, cached=True) -> "FormMeta": def mask_protected_fields(meta): - if ( - frappe.flags.in_patch - or frappe.flags.in_install - or frappe.flags.in_migrate - or frappe.flags.in_setup_wizard - ): - return meta - for df in meta.fields: if df.mask and not meta.has_permlevel_access_to( fieldname=df.fieldname, df=df, permission_type="mask" ): # store orignal fieldtype and change fieldtype to Data - df.read_only = 1 df.mask_readonly = 1 - df.set("old_fieldtype", df.get("old_fieldtype") or df.fieldtype) - if df.fieldtype != "Data": - df.fieldtype = "Data" return meta diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 9621b44402..3832052e87 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -376,8 +376,7 @@ def export_query(): form_params = get_form_params() form_params["limit_page_length"] = None - # remove as_list param because key is needed for data masking - # form_params["as_list"] = True + form_params["as_list"] = True doctype = form_params.pop("doctype") if isinstance(form_params["fields"], list): form_params["fields"].append("owner") @@ -421,10 +420,6 @@ def export_query(): data = [[_("Sr"), *labels]] processed_data = [] - # convert ret to a list of lists if it is a list of dicts - if isinstance(ret, list) and ret and isinstance(ret[0], dict): - ret = [[value for value in row.values()] for row in ret] - if frappe.local.lang == "en" or not translate_values: data.extend([i + 1, *list(row)] for i, row in enumerate(ret)) elif translate_values: diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 3ba56ee575..1180fd1332 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -231,24 +231,36 @@ class DatabaseQuery: if not masked_fields: return result - for row in result: - for field in masked_fields: - if field.fieldname in row: - val = row[field.fieldname] - if not val: - continue + if self.as_list: + masked_result = [] + field_index_map = {} + for idx, field in enumerate(self.fields): + # handle aliases (e.g. `tabSI`.`posting_date` as posting_date) + if " as " in field.lower(): + alias = field.split(" as ")[1].strip(" '") + field_index_map[alias] = idx + else: + # extract last part after `.` + col = field.split(".")[-1].strip("`") + field_index_map[col] = idx + # if as_list then we don't have field names in the result so we need to mask by position + for row in result: + row = list(row) # convert tuple to list mutable + for field in masked_fields: + if field.fieldname in field_index_map: + idx = field_index_map[field.fieldname] + val = row[idx] + row[idx] = mask_field_value(field, val) + + masked_result.append(tuple(row)) # convert back to tuple + result = masked_result + else: + for row in result: + for field in masked_fields: + if field.fieldname in row: + val = row[field.fieldname] + row[field.fieldname] = mask_field_value(field, val) - if field.old_fieldtype == "Data" and field.options == "Phone": - row[field.fieldname] = val[:3] + "XXXXXX" - elif field.old_fieldtype == "Data" and field.options == "Email": - email = val.split("@") - row[field.fieldname] = "XXXXXX@" + email[1] - elif field.old_fieldtype == "Date": - row[field.fieldname] = "XX-XX-XXXX" - elif field.old_fieldtype == "Time": - row[field.fieldname] = "XX:XX" - else: - row[field.fieldname] = "XXXXXXXX" return result def get_masked_fields(self): @@ -660,15 +672,6 @@ from {tables} ) ) - # get_permitted_field = get_permitted_fields( - # doctype=self.doctype, - # parenttype=self.parent_doctype, - # permission_type=self.permission_map.get(self.doctype), - # ignore_virtual=True, - # ) - - print(self.doctype, self.permission_map.get(self.doctype), "permitted_fields \n\n\n\n") - # print(get_permitted_field, "get_permitted_field \n\n\n\n") permitted_child_table_fields = {} for i, field in enumerate(self.fields): @@ -1239,6 +1242,23 @@ from {tables} update_user_settings(self.doctype, user_settings) +def mask_field_value(field, val): + if not val: + return val + + if field.fieldtype == "Data" and field.options == "Phone": + return val[:3] + "XXXXXX" + elif field.fieldtype == "Data" and field.options == "Email": + email = val.split("@") + return "XXXXXX@" + email[1] if len(email) > 1 else "XXXXXX" + elif field.fieldtype == "Date": + return "XX-XX-XXXX" + elif field.fieldtype == "Time": + return "XX:XX" + else: + return "XXXXXXXX" + + def cast_name(column: str) -> str: """Casts name field to varchar for postgres diff --git a/frappe/model/document.py b/frappe/model/document.py index 9e342e7381..1825543ff2 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -273,70 +273,16 @@ class Document(BaseDocument): return self - def mask_field_value(self, field): - # TODO: use this method to mask value and remove other code - already_masked = False - - if field.fieldtype == "Data" and field.options == "Phone": - already_masked = True - self.set(field.fieldname, self.get(field.fieldname)[0:3] + "XXXXXXX") - - if field.fieldtype == "Data" and field.options == "Email": - already_masked = True - email = self.get(field.fieldname) - if email: - email = email.split("@") - self.set(field.fieldname, "XXXXXXXX" + "@" + email[1]) - - if field.fieldtype == "Date": - already_masked = True - date = self.get(field.fieldname) - if date: - self.set(field.fieldname, "XX-XX-XXXX") - - if field.fieldtype == "Time": - already_masked = True - date = self.get(field.fieldname) - if date: - self.set(field.fieldname, "XX:XX") - - if not already_masked: - self.set(field.fieldname, "XXXXXXXX") - def mask_fields(self): + from frappe.model.db_query import mask_field_value + mask_fields = frappe.get_meta(self.doctype).get_masked_fields() if not mask_fields: return for field in mask_fields: - already_masked = False - - # if field type is Data and option is Phone the mask all value except last 3 - if field.old_fieldtype == "Data" and field.options == "Phone": - already_masked = True - self.set(field.fieldname, self.get(field.fieldname)[0:3] + "XXXXXXX") - - if field.old_fieldtype == "Data" and field.options == "Email": - already_masked = True - email = self.get(field.fieldname) - if email: - email = email.split("@") - self.set(field.fieldname, "XXXXXXXX" + "@" + email[1]) - - if field.old_fieldtype == "Date": - already_masked = True - date = self.get(field.fieldname) - if date: - self.set(field.fieldname, "XX-XX-XXXX") - - if field.old_fieldtype == "Time": - already_masked = True - date = self.get(field.fieldname) - if date: - self.set(field.fieldname, "XX:XXXX") - - if not already_masked: - self.set(field.fieldname, "XXXXXXXX") + val = self.get(field.fieldname) + self.set(field.fieldname, mask_field_value(field, val)) def load_children_from_db(self): is_doctype = self.doctype == "DocType" @@ -981,7 +927,6 @@ class Document(BaseDocument): return # check for child tables - print(high_permlevel_fields, "high_permlevel_fields \n\n\n") for df in self.meta.get_table_fields(): high_permlevel_fields = frappe.get_meta(df.options).get_high_permlevel_fields() if high_permlevel_fields: diff --git a/frappe/model/meta.py b/frappe/model/meta.py index c2405dd960..11dc18b7df 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -88,14 +88,13 @@ def get_meta(doctype: "str | DocType", cached: bool = True) -> "_Meta": meta = Meta(doctype) - key = f"doctype_meta::{meta.name}" - frappe.client_cache.set_value(key, meta) - if meta.name not in meta.special_doctypes: from frappe.desk.form.meta import mask_protected_fields meta = mask_protected_fields(meta) + key = f"doctype_meta::{meta.name}" + frappe.client_cache.set_value(key, meta) return meta diff --git a/frappe/public/js/frappe/form/controls/base_control.js b/frappe/public/js/frappe/form/controls/base_control.js index dfb241ca95..d56f269f80 100644 --- a/frappe/public/js/frappe/form/controls/base_control.js +++ b/frappe/public/js/frappe/form/controls/base_control.js @@ -9,7 +9,7 @@ frappe.ui.form.Control = class BaseControl { make() { this.make_wrapper(); this.$wrapper - .attr("data-fieldtype", this.df?.old_fieldtype || this.df.fieldtype) + .attr("data-fieldtype", this.df.fieldtype) .attr("data-fieldname", this.df.fieldname); this.wrapper = this.$wrapper.get(0); this.wrapper.fieldobj = this; // reference for event handlers diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index c068502fc7..b28d7cdfec 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -413,7 +413,7 @@ frappe.form.get_formatter = function (fieldtype) { }; frappe.format = function (value, df, options, doc) { - if (!df) df = { fieldtype: "Data" }; + if (!df || df?.mask_readonly) df = { fieldtype: "Data" }; if (df.fieldname == "_user_tags") df = { ...df, fieldtype: "Tag" }; var fieldtype = df.fieldtype || "Data"; diff --git a/frappe/public/js/frappe/form/layout.js b/frappe/public/js/frappe/form/layout.js index 1738d23863..46ee93f645 100644 --- a/frappe/public/js/frappe/form/layout.js +++ b/frappe/public/js/frappe/form/layout.js @@ -248,7 +248,7 @@ frappe.ui.form.Layout = class Layout { init_field(df, parent, render = false) { if (df.mask && df.mask_readonly) { if (df.fieldtype !== "Data") { - df.original_fieldtype = df.fieldtype; + df.read_only = 1; df.fieldtype = "Data"; } }