From 2b2a608babfa8a2eec50712eb620df73f7278cdc Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Tue, 3 Jun 2014 17:21:45 +0530 Subject: [PATCH] Apply User Permissions optional in script and query report, option to pass user in has_permission --- frappe/__init__.py | 8 +-- frappe/core/doctype/event/event.py | 8 +-- frappe/core/doctype/report/report.json | 11 +++- frappe/core/doctype/report/report.py | 10 ++-- frappe/core/doctype/todo/todo.py | 19 ++++--- frappe/core/doctype/user/user.json | 7 ++- .../permitted_documents_for_user/__init__.py | 0 .../permitted_documents_for_user.js | 29 +++++++++++ .../permitted_documents_for_user.json | 15 ++++++ .../permitted_documents_for_user.py | 51 +++++++++++++++++++ frappe/core/report/todo/todo.json | 5 +- frappe/model/db_query.py | 11 ++-- frappe/patches.txt | 3 +- frappe/permissions.py | 32 +++++++----- frappe/public/js/frappe/form/control.js | 6 ++- frappe/widgets/query_report.py | 51 ++++++++++--------- 16 files changed, 190 insertions(+), 76 deletions(-) create mode 100644 frappe/core/report/permitted_documents_for_user/__init__.py create mode 100644 frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.js create mode 100644 frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.json create mode 100644 frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py diff --git a/frappe/__init__.py b/frappe/__init__.py index 8edbd28f04..3ca7ab6b8a 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -291,9 +291,9 @@ def get_roles(username=None): else: return User(username).get_roles() -def has_permission(doctype, ptype="read", doc=None): +def has_permission(doctype, ptype="read", doc=None, user=None): import frappe.permissions - return frappe.permissions.has_permission(doctype, ptype, doc) + return frappe.permissions.has_permission(doctype, ptype, doc, user=user) def is_table(doctype): tables = cache().get_value("is_table") @@ -573,13 +573,13 @@ def build_match_conditions(doctype, as_condition=True): def get_list(doctype, filters=None, fields=None, or_filters=None, docstatus=None, group_by=None, order_by=None, limit_start=0, limit_page_length=None, - as_list=False, debug=False, ignore_permissions=False): + as_list=False, debug=False, ignore_permissions=False, user=None): import frappe.model.db_query return frappe.model.db_query.DatabaseQuery(doctype).execute(filters=filters, fields=fields, docstatus=docstatus, or_filters=or_filters, group_by=group_by, order_by=order_by, limit_start=limit_start, limit_page_length=limit_page_length, as_list=as_list, debug=debug, - ignore_permissions=ignore_permissions) + ignore_permissions=ignore_permissions, user=user) run_query = get_list diff --git a/frappe/core/doctype/event/event.py b/frappe/core/doctype/event/event.py index ebed036f47..08042022c2 100644 --- a/frappe/core/doctype/event/event.py +++ b/frappe/core/doctype/event/event.py @@ -27,14 +27,14 @@ def get_permission_query_conditions(): "roles": "', '".join(frappe.get_roles(frappe.session.user)) } -def has_permission(doc): - if doc.event_type=="Public" or doc.owner==frappe.session.user: +def has_permission(doc, user): + if doc.event_type=="Public" or doc.owner==user: return True - if doc.get("event_individuals", {"person":frappe.session.user}): + if doc.get("event_individuals", {"person": user}): return True - if doc.get("event_roles", {"role":("in", frappe.get_roles())}): + if doc.get("event_roles", {"role":("in", frappe.get_roles(user))}): return True return False diff --git a/frappe/core/doctype/report/report.json b/frappe/core/doctype/report/report.json index 26ed9ef6e9..3851d07c40 100644 --- a/frappe/core/doctype/report/report.json +++ b/frappe/core/doctype/report/report.json @@ -61,6 +61,15 @@ "permlevel": 0, "read_only": 0 }, + { + "default": "1", + "depends_on": "eval:[\"Query Report\", \"Script Report\"].indexOf(doc.report_type)!==-1", + "fieldname": "apply_user_permissions", + "fieldtype": "Check", + "in_list_view": 0, + "label": "Apply User Permissions", + "permlevel": 0 + }, { "fieldname": "section_break_6", "fieldtype": "Section Break", @@ -101,7 +110,7 @@ ], "icon": "icon-table", "idx": 1, - "modified": "2014-05-27 03:49:17.001234", + "modified": "2014-06-03 07:25:41.509885", "modified_by": "Administrator", "module": "Core", "name": "Report", diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py index 26c646529c..3be887e102 100644 --- a/frappe/core/doctype/report/report.py +++ b/frappe/core/doctype/report/report.py @@ -12,26 +12,26 @@ class Report(Document): """only administrator can save standard report""" if not self.module: self.module = frappe.db.get_value("DocType", self.ref_doctype, "module") - + if not self.is_standard: self.is_standard = "No" if frappe.session.user=="Administrator" and getattr(conf, 'developer_mode',0)==1: self.is_standard = "Yes" if self.is_standard == "Yes" and frappe.session.user!="Administrator": - frappe.msgprint(_("Only Administrator can save a standard report. Please rename and save."), + frappe.msgprint(_("Only Administrator can save a standard report. Please rename and save."), raise_exception=True) if self.report_type in ("Query Report", "Script Report") \ and frappe.session.user!="Administrator": frappe.msgprint(_("Only Administrator allowed to create Query / Script Reports"), raise_exception=True) - + def on_update(self): self.export_doc() - + def export_doc(self): from frappe.modules.export_file import export_to_files if self.is_standard == 'Yes' and (conf.get('developer_mode') or 0) == 1: - export_to_files(record_list=[['Report', self.name]], + export_to_files(record_list=[['Report', self.name]], record_module=self.module) diff --git a/frappe/core/doctype/todo/todo.py b/frappe/core/doctype/todo/todo.py index 936e676c07..caab426276 100644 --- a/frappe/core/doctype/todo/todo.py +++ b/frappe/core/doctype/todo/todo.py @@ -14,24 +14,24 @@ class ToDo(Document): cur_status = frappe.db.get_value("ToDo", self.name, "status") if cur_status != self.status: self.add_comment(frappe._("Assignment Status Changed")) - + def add_comment(self, text): if not self.reference_type and self.reference_name: return - + comment = frappe.get_doc({ "doctype":"Comment", "comment_by": frappe.session.user, "comment_doctype": self.reference_type, "comment_docname": self.reference_name, - "comment": """
{text}: + "comment": """
{text}: {status}: {description}
""".format(text=text, status = frappe._(self.status), name = self.name, description = self.description) }).insert(ignore_permissions=True) - - + + # todo is viewable if either owner or assigned_to or System Manager in roles def get_permission_query_conditions(): @@ -39,10 +39,9 @@ def get_permission_query_conditions(): return None else: return """(tabToDo.owner = '{user}' or tabToDo.assigned_by = '{user}')""".format(user=frappe.session.user) - -def has_permission(doc): - if "System Manager" in frappe.get_roles(): + +def has_permission(doc, user): + if "System Manager" in frappe.get_roles(user): return True else: - return doc.owner==frappe.session.user or doc.assigned_by==frappe.session.user - \ No newline at end of file + return doc.owner==user or doc.assigned_by==user diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json index 354a8515d4..8ab84a2e2a 100644 --- a/frappe/core/doctype/user/user.json +++ b/frappe/core/doctype/user/user.json @@ -475,14 +475,13 @@ }, { "apply_user_permissions": 0, - "cancel": 0, "create": 0, "delete": 0, - "email": 1, + "email": 0, "permlevel": 0, - "print": 1, + "print": 0, "read": 1, - "report": 1, + "report": 0, "role": "All", "submit": 0, "write": 0 diff --git a/frappe/core/report/permitted_documents_for_user/__init__.py b/frappe/core/report/permitted_documents_for_user/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.js b/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.js new file mode 100644 index 0000000000..f379963284 --- /dev/null +++ b/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.js @@ -0,0 +1,29 @@ +// Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +// MIT License. See license.txt + +frappe.query_reports["Permitted Documents For User"] = { + "filters": [ + { + "fieldname": "user", + "label": __("User"), + "fieldtype": "Link", + "options": "User", + "reqd": 1 + }, + { + "fieldname": "doctype", + "label": __("DocType"), + "fieldtype": "Link", + "options": "DocType", + "reqd": 1, + "get_query": function() { + return { + "query": "frappe.core.report.permitted_documents_for_user.permitted_documents_for_user.query_doctypes", + "filters": { + "user": frappe.query_report.filters_by_name.user.get_value() + } + } + } + } + ] +} diff --git a/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.json b/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.json new file mode 100644 index 0000000000..458baa936c --- /dev/null +++ b/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.json @@ -0,0 +1,15 @@ +{ + "apply_user_permissions": 1, + "creation": "2014-06-03 05:20:35.218263", + "docstatus": 0, + "doctype": "Report", + "is_standard": "Yes", + "modified": "2014-06-03 07:18:17.218526", + "modified_by": "Administrator", + "module": "Core", + "name": "Permitted Documents For User", + "owner": "Administrator", + "ref_doctype": "User", + "report_name": "Permitted Documents For User", + "report_type": "Script Report" +} \ No newline at end of file diff --git a/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py b/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py new file mode 100644 index 0000000000..3a815577de --- /dev/null +++ b/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py @@ -0,0 +1,51 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _, throw +import frappe.utils.user +from frappe.permissions import check_admin_or_system_manager + +def execute(filters=None): + user, doctype = filters.get("user"), filters.get("doctype") + validate(user, doctype) + + columns, fields = get_columns_and_fields(doctype) + data = frappe.get_list(doctype, fields=fields, as_list=True, user=user) + + return columns, data + +def validate(user, doctype): + # check if current user is System Manager + check_admin_or_system_manager() + + if not user: + throw(_("Please specify user")) + + if not doctype: + throw(_("Please specify doctype")) + +def get_columns_and_fields(doctype): + columns = ["Name:Link/{}:200".format(doctype)] + fields = ["name"] + for df in frappe.get_meta(doctype).fields: + if df.in_list_view: + fields.append(df.fieldname) + fieldtype = "Link/{}".format(df.options) if df.fieldtype=="Link" else df.fieldtype + columns.append("{label}:{fieldtype}:{width}".format(label=df.label, fieldtype=fieldtype, width=df.width or 100)) + + return columns, fields + +def query_doctypes(doctype, txt, searchfield, start, page_len, filters): + user = filters.get("user") + user_obj = frappe.utils.user.User(user) + user_obj.build_permissions() + can_read = user_obj.can_read + + out = [] + for dt in can_read: + if txt.lower().replace("%", "") in dt.lower(): + out.append([dt]) + + return out diff --git a/frappe/core/report/todo/todo.json b/frappe/core/report/todo/todo.json index 768162f126..f808571789 100644 --- a/frappe/core/report/todo/todo.json +++ b/frappe/core/report/todo/todo.json @@ -1,10 +1,11 @@ { - "creation": "2013-02-25 14:26:30.000000", + "apply_user_permissions": 1, + "creation": "2013-02-25 14:26:30", "docstatus": 0, "doctype": "Report", "idx": 1, "is_standard": "Yes", - "modified": "2014-03-07 15:30:27.000000", + "modified": "2014-06-03 07:18:17.374222", "modified_by": "Administrator", "module": "Core", "name": "ToDo", diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 68e7999e32..e98b06028d 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -17,12 +17,13 @@ class DatabaseQuery(object): self.conditions = [] self.ignore_permissions = False self.fields = ["name"] + self.user = None def execute(self, query=None, filters=None, fields=None, or_filters=None, docstatus=None, group_by=None, order_by=None, limit_start=0, limit_page_length=20, as_list=False, with_childnames=False, debug=False, - ignore_permissions=False): - if not frappe.has_permission(self.doctype, "read"): + ignore_permissions=False, user=None): + if not frappe.has_permission(self.doctype, "read", user=user): raise frappe.PermissionError if fields: @@ -38,7 +39,7 @@ class DatabaseQuery(object): self.debug = debug self.as_list = as_list self.ignore_permissions = ignore_permissions - + self.user = user or frappe.session.user if query: return self.run_custom_query(query) @@ -216,10 +217,10 @@ class DatabaseQuery(object): if not self.tables: self.extract_tables() # apply user permissions? - role_permissions = frappe.permissions.get_role_permissions(frappe.get_meta(self.doctype)) + role_permissions = frappe.permissions.get_role_permissions(frappe.get_meta(self.doctype), user=self.user) if role_permissions.get("apply_user_permissions", {}).get("read"): # get user permissions - user_permissions = frappe.defaults.get_user_permissions() + user_permissions = frappe.defaults.get_user_permissions(self.user) self.add_user_permissions(user_permissions) if as_condition: diff --git a/frappe/patches.txt b/frappe/patches.txt index 151dc61d95..5e5e4ee87c 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -5,7 +5,7 @@ execute:frappe.reload_doc('core', 'doctype', 'doctype', force=True) #2014-01-24 execute:frappe.reload_doc('core', 'doctype', 'docfield', force=True) #2014-03-01 execute:frappe.reload_doc('core', 'doctype', 'docperm') #2013-13-26 execute:frappe.reload_doc('core', 'doctype', 'page') #2013-13-26 -execute:frappe.reload_doc('core', 'doctype', 'report') #2013-13-26 +execute:frappe.reload_doc('core', 'doctype', 'report') #2014-06-03 execute:frappe.reload_doc('core', 'doctype', 'version') #2014-02-21 execute:frappe.db.sql("alter table `tabSessions` modify `user` varchar(255), engine=InnoDB") execute:frappe.db.sql("delete from `tabDocField` where parent='0'") @@ -33,3 +33,4 @@ frappe.patches.v4_0.update_custom_field_insert_after frappe.patches.v4_0.set_user_permissions frappe.patches.v4_0.create_custom_field_for_owner_match frappe.patches.v4_0.enable_scheduler_in_system_settings +execute:frappe.db.sql("update tabReport set apply_user_permissions=1") #2014-06-03 diff --git a/frappe/permissions.py b/frappe/permissions.py index f5146e90c0..9ababa96bc 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -9,13 +9,16 @@ from frappe.utils import cint rights = ("read", "write", "create", "delete", "submit", "cancel", "amend", "print", "email", "report", "import", "export", "set_user_permissions") -def check_admin_or_system_manager(): - if ("System Manager" not in frappe.get_roles()) and \ - (frappe.session.user!="Administrator"): +def check_admin_or_system_manager(user=None): + if not user: user = frappe.session.user + + if ("System Manager" not in frappe.get_roles(user)) and (user!="Administrator"): frappe.throw(_("Not permitted"), frappe.PermissionError) -def has_permission(doctype, ptype="read", doc=None, verbose=True): +def has_permission(doctype, ptype="read", doc=None, verbose=True, user=None): """check if user has permission""" + if not user: user = frappe.session.user + if frappe.is_table(doctype): return True @@ -27,10 +30,10 @@ def has_permission(doctype, ptype="read", doc=None, verbose=True): if ptype=="import" and not cint(meta.allow_import): return False - if frappe.session.user=="Administrator": + if user=="Administrator": return True - role_permissions = get_role_permissions(meta) + role_permissions = get_role_permissions(meta, user=user) if not role_permissions.get(ptype): return False @@ -38,17 +41,16 @@ def has_permission(doctype, ptype="read", doc=None, verbose=True): if isinstance(doc, basestring): doc = frappe.get_doc(meta.name, doc) - if not user_has_permission(doc, verbose=verbose): + if not user_has_permission(doc, verbose=verbose, user=user): return False - if not has_controller_permissions(doc): + if not has_controller_permissions(doc, user=user): return False return True def get_role_permissions(meta, user=None): - if not user: - user = frappe.session.user + if not user: user = frappe.session.user cache_key = (meta.name, user) if not frappe.local.role_permissions.get(cache_key): @@ -71,9 +73,9 @@ def get_role_permissions(meta, user=None): return frappe.local.role_permissions[cache_key] -def user_has_permission(doc, verbose=True): +def user_has_permission(doc, verbose=True, user=None): from frappe.defaults import get_user_permissions - user_permissions = get_user_permissions() + user_permissions = get_user_permissions(user) user_permissions_keys = user_permissions.keys() def check_user_permission(d): @@ -99,9 +101,11 @@ def user_has_permission(doc, verbose=True): return _user_has_permission -def has_controller_permissions(doc): +def has_controller_permissions(doc, user=None): + if not user: user = frappe.session.user + for method in frappe.get_hooks("has_permission").get(doc.doctype, []): - if not frappe.call(frappe.get_attr(method), doc=doc): + if not frappe.call(frappe.get_attr(method), doc=doc, user=user): return False return True diff --git a/frappe/public/js/frappe/form/control.js b/frappe/public/js/frappe/form/control.js index 4176b1a9c8..34939d351b 100644 --- a/frappe/public/js/frappe/form/control.js +++ b/frappe/public/js/frappe/form/control.js @@ -894,7 +894,11 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ select: function(event, ui) { me.autocomplete_open = false; if(ui.item.make_new) { - me.frm.new_doc(me.df.options, me); + if (me.frm) { + me.frm.new_doc(me.df.options, me); + } else { + new_doc(me.df.options); + } return false; } diff --git a/frappe/widgets/query_report.py b/frappe/widgets/query_report.py index d6e5d2f7f3..94f9918c26 100644 --- a/frappe/widgets/query_report.py +++ b/frappe/widgets/query_report.py @@ -16,58 +16,58 @@ def get_report_doc(report_name): doc = frappe.get_doc("Report", report_name) if not doc.has_permission("read"): raise frappe.PermissionError("You don't have access to: {report}".format(report=report_name)) - + if not frappe.has_permission(doc.ref_doctype, "report"): raise frappe.PermissionError("You don't have access to get a report on: {doctype}".format( doctype=doc.ref_doctype)) - + return doc @frappe.whitelist() def get_script(report_name): report = get_report_doc(report_name) - + module = frappe.db.get_value("DocType", report.ref_doctype, "module") module_path = get_module_path(module) report_folder = os.path.join(module_path, "report", scrub(report.name)) script_path = os.path.join(report_folder, scrub(report.name) + ".js") - + script = None if os.path.exists(script_path): with open(script_path, "r") as script: script = script.read() - + if not script and report.javascript: script = report.javascript - + if not script: script = "frappe.query_reports['%s']={}" % report_name - + # load translations if frappe.lang != "en": frappe.response["__messages"] = frappe.get_lang_dict("report", report_name) - + return script @frappe.whitelist() def run(report_name, filters=()): report = get_report_doc(report_name) - + if filters and isinstance(filters, basestring): filters = json.loads(filters) if not frappe.has_permission(report.ref_doctype, "report"): - frappe.msgprint(_("Must have report permission to access this report."), + frappe.msgprint(_("Must have report permission to access this report."), raise_exception=True) - + if report.report_type=="Query Report": if not report.query: frappe.msgprint(_("Must specify a Query to run"), raise_exception=True) - - + + if not report.query.lower().startswith("select"): frappe.msgprint(_("Query must be a SELECT"), raise_exception=True) - + result = [list(t) for t in frappe.db.sql(report.query, filters)] columns = [c[0] for c in frappe.db.get_description()] else: @@ -76,17 +76,18 @@ def run(report_name, filters=()): method_name = frappe.local.module_app[scrub(module)] + "." + scrub(module) \ + ".report." + scrub(report.name) + "." + scrub(report.name) + ".execute" columns, result = frappe.get_attr(method_name)(frappe._dict(filters)) - - result = get_filtered_data(report.ref_doctype, columns, result) - + + if report.apply_user_permissions: + result = get_filtered_data(report.ref_doctype, columns, result) + if cint(report.add_total_row) and result: result = add_total_row(result, columns) - + return { "result": result, "columns": columns } - + def add_total_row(result, columns): total_row = [""]*len(columns) has_percent = [] @@ -98,14 +99,14 @@ def add_total_row(result, columns): total_row[i] = flt(total_row[i]) + flt(row[i]) if col[1] == "Percent" and i not in has_percent: has_percent.append(i) - + for i in has_percent: total_row[i] = total_row[i] / len(result) - + first_col = columns[0].split(":") if len(first_col) > 1 and first_col[1] not in ["Currency", "Int", "Float", "Percent"]: total_row[0] = "Total" - + result.append(total_row) return result @@ -114,12 +115,12 @@ def get_filtered_data(ref_doctype, columns, data): linked_doctypes = get_linked_doctypes(columns) match_filters = get_user_match_filters(linked_doctypes, ref_doctype) - + if match_filters: matched_columns = get_matched_columns(linked_doctypes, match_filters) for row in data: match = True - + if not ("owner" in match_filters and matched_columns.get("user", None)==match_filters["owner"]): for col, idx in matched_columns.items(): if row[idx] not in match_filters[col]: @@ -165,4 +166,4 @@ def get_matched_columns(linked_doctypes, match_filters): if link_field in match_filters: col_idx_map[link_field] = idx - return col_idx_map \ No newline at end of file + return col_idx_map