diff --git a/frappe/boot.py b/frappe/boot.py index d413d03fb6..1cc8202123 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -17,6 +17,7 @@ from frappe.utils.change_log import get_versions from frappe.translate import get_lang_dict from frappe.email.inbox import get_email_accounts from frappe.core.doctype.feedback_trigger.feedback_trigger import get_enabled_feedback_trigger +from frappe.core.doctype.user_permission.user_permission import get_user_permissions def get_bootinfo(): """build and return boot info""" @@ -30,6 +31,7 @@ def get_bootinfo(): # system info bootinfo.sysdefaults = frappe.defaults.get_defaults() + bootinfo.user_permissions = get_user_permissions() bootinfo.server_date = frappe.utils.nowdate() if frappe.session['user'] != 'Guest': diff --git a/frappe/core/doctype/communication/feed.py b/frappe/core/doctype/communication/feed.py index ded84a469c..2d939447cd 100644 --- a/frappe/core/doctype/communication/feed.py +++ b/frappe/core/doctype/communication/feed.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals import frappe -import frappe.defaults import frappe.permissions from frappe.model.document import Document from frappe.utils import get_fullname @@ -68,7 +67,7 @@ def get_feed_match_conditions(user=None, force=True): conditions = ['`tabCommunication`.owner="{user}" or `tabCommunication`.reference_owner="{user}"'.format(user=frappe.db.escape(user))] - user_permissions = frappe.defaults.get_user_permissions(user) + user_permissions = frappe.permissions.get_user_permissions(user) can_read = frappe.get_user().get_can_read() can_read_doctypes = ['"{}"'.format(doctype) for doctype in diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json index 87d85449ba..bbdf75c085 100644 --- a/frappe/core/doctype/system_settings/system_settings.json +++ b/frappe/core/doctype/system_settings/system_settings.json @@ -527,7 +527,7 @@ "bold": 0, "collapsible": 1, "columns": 0, - "fieldname": "security", + "fieldname": "permissions", "fieldtype": "Section Break", "hidden": 0, "ignore_user_permissions": 0, @@ -536,10 +536,11 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Security", + "label": "Permissions", "length": 0, "no_copy": 0, "permlevel": 0, + "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, @@ -556,10 +557,9 @@ "bold": 0, "collapsible": 0, "columns": 0, - "default": "06:00", - "description": "Session Expiry in Hours e.g. 06:00", - "fieldname": "session_expiry", - "fieldtype": "Data", + "description": "If Apply User Permissions is checked for Report DocType but no User Permissions are defined for Report for a User, then all Reports are shown to that User", + "fieldname": "ignore_user_permissions_if_missing", + "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -567,11 +567,11 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Session Expiry", + "label": "Ignore User Permissions If Missing", "length": 0, "no_copy": 0, - "options": "", "permlevel": 0, + "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, @@ -588,10 +588,10 @@ "bold": 0, "collapsible": 0, "columns": 0, - "default": "720:00", - "description": "In Hours", - "fieldname": "session_expiry_mobile", - "fieldtype": "Data", + "default": "0", + "description": "If Apply Strict User Permission is checked and User Permission is defined for a DocType for a User, then all the documents where value of the link is blank, will not be shown to that User", + "fieldname": "apply_strict_user_permissions", + "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -599,7 +599,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Session Expiry Mobile", + "label": "Apply Strict User Permissions", "length": 0, "no_copy": 0, "permlevel": 0, @@ -614,16 +614,45 @@ "set_only_once": 0, "unique": 0 }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 1, + "columns": 0, + "fieldname": "security", + "fieldtype": "Section Break", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Security", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, { "allow_bulk_edit": 0, "allow_on_submit": 0, "bold": 0, "collapsible": 0, "columns": 0, - "default": "0", - "description": "If enabled, the password strength will be enforced based on the Minimum Password Score value. A value of 2 being medium strong and 4 being very strong.", - "fieldname": "enable_password_policy", - "fieldtype": "Check", + "default": "06:00", + "description": "Session Expiry in Hours e.g. 06:00", + "fieldname": "session_expiry", + "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -631,11 +660,11 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Enable Password Policy", + "label": "Session Expiry", "length": 0, "no_copy": 0, + "options": "", "permlevel": 0, - "precision": "", "print_hide": 0, "print_hide_if_no_value": 0, "read_only": 0, @@ -652,10 +681,10 @@ "bold": 0, "collapsible": 0, "columns": 0, - "default": "2", - "depends_on": "eval:doc.enable_password_policy==1", - "fieldname": "minimum_password_score", - "fieldtype": "Select", + "default": "720:00", + "description": "In Hours", + "fieldname": "session_expiry_mobile", + "fieldtype": "Data", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -663,10 +692,9 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Minimum Password Score", + "label": "Session Expiry Mobile", "length": 0, "no_copy": 0, - "options": "2\n4", "permlevel": 0, "precision": "", "print_hide": 0, @@ -685,8 +713,10 @@ "bold": 0, "collapsible": 0, "columns": 0, - "fieldname": "column_break_13", - "fieldtype": "Column Break", + "default": "0", + "description": "If enabled, the password strength will be enforced based on the Minimum Password Score value. A value of 2 being medium strong and 4 being very strong.", + "fieldname": "enable_password_policy", + "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -694,6 +724,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, + "label": "Enable Password Policy", "length": 0, "no_copy": 0, "permlevel": 0, @@ -714,9 +745,10 @@ "bold": 0, "collapsible": 0, "columns": 0, - "description": "Note: Multiple sessions will be allowed in case of mobile device", - "fieldname": "deny_multiple_sessions", - "fieldtype": "Check", + "default": "2", + "depends_on": "eval:doc.enable_password_policy==1", + "fieldname": "minimum_password_score", + "fieldtype": "Select", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -724,9 +756,10 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Allow only one session per user", + "label": "Minimum Password Score", "length": 0, "no_copy": 0, + "options": "2\n4", "permlevel": 0, "precision": "", "print_hide": 0, @@ -745,9 +778,8 @@ "bold": 0, "collapsible": 0, "columns": 0, - "description": "If Apply User Permissions is checked for Report DocType but no User Permissions are defined for Report for a User, then all Reports are shown to that User", - "fieldname": "ignore_user_permissions_if_missing", - "fieldtype": "Check", + "fieldname": "column_break_13", + "fieldtype": "Column Break", "hidden": 0, "ignore_user_permissions": 0, "ignore_xss_filter": 0, @@ -755,7 +787,6 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Ignore User Permissions If Missing", "length": 0, "no_copy": 0, "permlevel": 0, @@ -776,9 +807,8 @@ "bold": 0, "collapsible": 0, "columns": 0, - "default": "0", - "description": "If Apply Strict User Permission is checked and User Permission is defined for a DocType for a User, then all the documents where value of the link is blank, will not be shown to that User", - "fieldname": "apply_strict_user_permissions", + "description": "Note: Multiple sessions will be allowed in case of mobile device", + "fieldname": "deny_multiple_sessions", "fieldtype": "Check", "hidden": 0, "ignore_user_permissions": 0, @@ -787,7 +817,7 @@ "in_global_search": 0, "in_list_view": 0, "in_standard_filter": 0, - "label": "Apply Strict User Permissions", + "label": "Allow only one session per user", "length": 0, "no_copy": 0, "permlevel": 0, @@ -997,7 +1027,7 @@ "issingle": 1, "istable": 0, "max_attachments": 0, - "modified": "2017-06-23 07:48:10.453011", + "modified": "2017-07-20 22:57:56.466867", "modified_by": "Administrator", "module": "Core", "name": "System Settings", @@ -1032,4 +1062,4 @@ "sort_order": "ASC", "track_changes": 1, "track_seen": 0 -} +} \ No newline at end of file diff --git a/frappe/core/doctype/system_settings/system_settings.py b/frappe/core/doctype/system_settings/system_settings.py index 8f64feda9f..f7ecfc00bb 100644 --- a/frappe/core/doctype/system_settings/system_settings.py +++ b/frappe/core/doctype/system_settings/system_settings.py @@ -35,6 +35,7 @@ class SystemSettings(Document): frappe.cache().delete_value('system_settings') frappe.cache().delete_value('time_zone') + frappe.local.system_settings = {} @frappe.whitelist() def load(): diff --git a/frappe/core/doctype/system_settings/test_system_settings.js b/frappe/core/doctype/system_settings/test_system_settings.js new file mode 100644 index 0000000000..53edaba99d --- /dev/null +++ b/frappe/core/doctype/system_settings/test_system_settings.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: System Settings", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially('System Settings', [ + // insert a new System Settings + () => frappe.tests.make([ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index aa7f7940e3..49c1f8b437 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -59,9 +59,13 @@ frappe.ui.form.on('User', { frappe.route_options = { "user": doc.name }; - frappe.set_route("user-permissions"); + frappe.set_route('List', 'User Permission'); }, null, "btn-default") + frm.add_custom_button(__('View Permitted Documents'), + () => frappe.set_route('query-report', 'Permitted Documents For User', + {user: frm.doc.name})); + frm.toggle_display(['sb1', 'sb3', 'modules_access'], true); } diff --git a/frappe/core/doctype/user_permission/__init__.py b/frappe/core/doctype/user_permission/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/doctype/user_permission/test_user_permission.js b/frappe/core/doctype/user_permission/test_user_permission.js new file mode 100644 index 0000000000..1770dddf81 --- /dev/null +++ b/frappe/core/doctype/user_permission/test_user_permission.js @@ -0,0 +1,23 @@ +/* eslint-disable */ +// rename this file from _test_[name] to test_[name] to activate +// and remove above this line + +QUnit.test("test: User Permission", function (assert) { + let done = assert.async(); + + // number of asserts + assert.expect(1); + + frappe.run_serially('User Permission', [ + // insert a new User Permission + () => frappe.tests.make([ + // values to be set + {key: 'value'} + ]), + () => { + assert.equal(cur_frm.doc.key, 'value'); + }, + () => done() + ]); + +}); diff --git a/frappe/core/doctype/user_permission/test_user_permission.py b/frappe/core/doctype/user_permission/test_user_permission.py new file mode 100644 index 0000000000..157fa44ae2 --- /dev/null +++ b/frappe/core/doctype/user_permission/test_user_permission.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +#import frappe +import unittest + +class TestUserPermission(unittest.TestCase): + pass diff --git a/frappe/core/doctype/user_permission/user_permission.js b/frappe/core/doctype/user_permission/user_permission.js new file mode 100644 index 0000000000..fbd6ed5616 --- /dev/null +++ b/frappe/core/doctype/user_permission/user_permission.js @@ -0,0 +1,10 @@ +// Copyright (c) 2017, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('User Permission', { + refresh: function(frm) { + frm.add_custom_button(__('View Permitted Documents'), + () => frappe.set_route('query-report', 'Permitted Documents For User', + {user: frm.doc.user})); + } +}); diff --git a/frappe/core/doctype/user_permission/user_permission.json b/frappe/core/doctype/user_permission/user_permission.json new file mode 100644 index 0000000000..2e67de5ce0 --- /dev/null +++ b/frappe/core/doctype/user_permission/user_permission.json @@ -0,0 +1,188 @@ +{ + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "beta": 0, + "creation": "2017-07-17 14:25:27.881871", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "", + "editable_grid": 1, + "engine": "InnoDB", + "fields": [ + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "user", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "User", + "length": 0, + "no_copy": 0, + "options": "User", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "allow", + "fieldtype": "Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Allow", + "length": 0, + "no_copy": 0, + "options": "DocType", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "fieldname": "for_value", + "fieldtype": "Dynamic Link", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 1, + "in_standard_filter": 1, + "label": "For Value", + "length": 0, + "no_copy": 0, + "options": "allow", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 1, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + }, + { + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 1, + "collapsible": 0, + "columns": 0, + "default": "1", + "description": "If you un-check this, you will have to apply manually for each Role + Document Type combination", + "fieldname": "apply_for_all_roles", + "fieldtype": "Check", + "hidden": 0, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_standard_filter": 0, + "label": "Apply for all Roles for this User", + "length": 0, + "no_copy": 0, + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "remember_last_selected_value": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "set_only_once": 0, + "unique": 0 + } + ], + "has_web_view": 0, + "hide_heading": 0, + "hide_toolbar": 0, + "idx": 0, + "image_view": 0, + "in_create": 0, + "is_submittable": 0, + "issingle": 0, + "istable": 0, + "max_attachments": 0, + "modified": "2017-07-27 22:55:58.647315", + "modified_by": "Administrator", + "module": "Core", + "name": "User Permission", + "name_case": "", + "owner": "Administrator", + "permissions": [ + { + "amend": 0, + "apply_user_permissions": 0, + "cancel": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "if_owner": 0, + "import": 0, + "permlevel": 0, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "set_user_permissions": 0, + "share": 1, + "submit": 0, + "write": 1 + } + ], + "quick_entry": 1, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "user", + "track_changes": 1, + "track_seen": 0 +} \ No newline at end of file diff --git a/frappe/core/doctype/user_permission/user_permission.py b/frappe/core/doctype/user_permission/user_permission.py new file mode 100644 index 0000000000..e14a900e19 --- /dev/null +++ b/frappe/core/doctype/user_permission/user_permission.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe, json +from frappe.model.document import Document +from frappe.permissions import (get_valid_perms, update_permission_property) +from frappe import _ + +class UserPermission(Document): + def on_update(self): + frappe.cache().delete_value('user_permissions') + + if self.apply_for_all_roles: + self.apply_user_permissions_to_all_roles() + + def apply_user_permissions_to_all_roles(self): + # add apply user permissions for all roles that + # for this doctype + def show_progress(i, l): + if l > 2: + frappe.publish_realtime("progress", + dict(progress=[i, l], title=_('Updating...')), + user=frappe.session.user) + + + roles = frappe.get_roles(self.user) + linked = frappe.db.sql('''select distinct parent from tabDocField + where fieldtype="Link" and options=%s''', self.allow) + for i, link in enumerate(linked): + doctype = link[0] + for perm in get_valid_perms(doctype, self.user): + # if the role is applicable to the user + show_progress(i+1, len(linked)) + if perm.role in roles: + if not perm.apply_user_permissions: + update_permission_property(doctype, perm.role, 0, + 'apply_user_permissions', '1') + + try: + user_permission_doctypes = json.loads(perm.user_permission_doctypes or '[]') + except ValueError: + user_permission_doctypes = [] + + if self.allow not in user_permission_doctypes: + user_permission_doctypes.append(self.allow) + update_permission_property(doctype, perm.role, 0, + 'user_permission_doctypes', json.dumps(user_permission_doctypes), validate=False) + + show_progress(len(linked), len(linked)) + + def on_trash(self): # pylint: disable=no-self-use + frappe.cache().delete_value('user_permissions') + +def get_user_permissions(user=None): + '''Get all users permissions for the user as a dict of doctype''' + if not user: + user = frappe.session.user + + out = frappe.cache().hget("user_permissions", user) + + if not out: + out = {} + try: + for perm in frappe.get_all('User Permission', + fields=['allow', 'for_value'], filters=dict(user=user)): + out.setdefault(perm.allow, []).append(perm.for_value) + + # add profile match + if user not in out.get("User", []): + out.setdefault("User", []).append(user) + + frappe.cache().hset("user_permissions", user, out) + except frappe.SQLError, e: + if e.args[0]==1146: + # called from patch + pass + + return out \ No newline at end of file diff --git a/frappe/core/page/permission_manager/permission_manager.js b/frappe/core/page/permission_manager/permission_manager.js index 400043a5fd..020db1d16b 100644 --- a/frappe/core/page/permission_manager/permission_manager.js +++ b/frappe/core/page/permission_manager/permission_manager.js @@ -21,6 +21,7 @@ frappe.pages['permission-manager'].refresh = function(wrapper) { frappe.PermissionEngine = Class.extend({ init: function(wrapper) { this.wrapper = wrapper; + this.page = wrapper.page; this.body = $(this.wrapper).find(".perm-engine"); this.make(); this.refresh(); @@ -55,6 +56,10 @@ frappe.PermissionEngine = Class.extend({ .change(function() { me.refresh(); }); + + this.page.add_inner_button(__('Set User Permissions'), () => { + return frappe.set_route('List', 'User Permission'); + }); this.set_from_route(); }, set_from_route: function() { @@ -133,11 +138,11 @@ frappe.PermissionEngine = Class.extend({ refresh: function() { var me = this; if(!me.doctype_select) { - this.body.html("

" + __("Loading") + "..."); + this.body.html("

" + __("Loading") + "...

"); return; } if(!me.get_doctype() && !me.get_role()) { - this.body.html("

"+__("Select Document Type or Role to start.")+""); + this.body.html("

"+__("Select Document Type or Role to start.")+"

"); return; } // get permissions @@ -247,10 +252,13 @@ frappe.PermissionEngine = Class.extend({ setup_user_permissions: function(d, role_cell) { var me = this; - d.help = frappe.render('', {}); + d.help = ``; var checkbox = this.add_check(role_cell, d, "apply_user_permissions") .removeClass("col-md-4") @@ -336,8 +344,8 @@ frappe.PermissionEngine = Class.extend({ var me = this; this.body.on("click", ".show-user-permissions", function() { - frappe.route_options = { doctype: me.get_doctype() || "" }; - frappe.set_route("user-permissions"); + frappe.route_options = { allow: me.get_doctype() || "" }; + frappe.set_route('List', 'User Permission'); }); this.body.on("click", "input[type='checkbox']", function() { diff --git a/frappe/core/page/permission_manager/permission_manager.py b/frappe/core/page/permission_manager/permission_manager.py index 626bb1c20d..ae3a3971e6 100644 --- a/frappe/core/page/permission_manager/permission_manager.py +++ b/frappe/core/page/permission_manager/permission_manager.py @@ -7,7 +7,7 @@ import frappe.defaults from frappe.modules.import_file import get_file_path, read_doc_from_file from frappe.translate import send_translations from frappe.permissions import (reset_perms, get_linked_doctypes, get_all_perms, - setup_custom_perms, add_permission) + setup_custom_perms, add_permission, update_permission_property) from frappe.core.doctype.doctype.doctype import (clear_permissions_cache, validate_permissions_for_doctype) from frappe import _ @@ -68,18 +68,8 @@ def add(parent, role, permlevel): @frappe.whitelist() def update(doctype, role, permlevel, ptype, value=None): frappe.only_for("System Manager") - - out = None - if setup_custom_perms(doctype): - out = 'refresh' - - name = frappe.get_value('Custom DocPerm', dict(parent=doctype, role=role, permlevel=permlevel)) - - frappe.db.sql("""update `tabCustom DocPerm` set `%s`=%s where name=%s"""\ - % (frappe.db.escape(ptype), '%s', '%s'), (value, name)) - validate_permissions_for_doctype(doctype) - - return out + out = update_permission_property(doctype, role, permlevel, ptype, value) + return 'refresh' if out else None @frappe.whitelist() def remove(doctype, role, permlevel): diff --git a/frappe/core/page/user_permissions/README.md b/frappe/core/page/user_permissions/README.md deleted file mode 100644 index e4fc779c6c..0000000000 --- a/frappe/core/page/user_permissions/README.md +++ /dev/null @@ -1 +0,0 @@ -Interface to set user defaults (DefaultValue). \ No newline at end of file diff --git a/frappe/core/page/user_permissions/__init__.py b/frappe/core/page/user_permissions/__init__.py deleted file mode 100644 index 0e57cb68c3..0000000000 --- a/frappe/core/page/user_permissions/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - diff --git a/frappe/core/page/user_permissions/user_permissions.js b/frappe/core/page/user_permissions/user_permissions.js deleted file mode 100644 index 5cb81900eb..0000000000 --- a/frappe/core/page/user_permissions/user_permissions.js +++ /dev/null @@ -1,365 +0,0 @@ -frappe.pages['user-permissions'].on_page_load = function(wrapper) { - var page = frappe.ui.make_app_page({ - parent: wrapper, - title: __("User Permissions Manager"), - icon: "fa fa-shield", - single_column: true - }); - - frappe.breadcrumbs.add("Setup"); - - $("
\ -

\ - " + __("Edit Role Permissions") + "\ -


\ -

"+__("Help for User Permissions")+":

\ -
    \ -
  1. " - + __("Apart from Role based Permission Rules, you can apply User Permissions based on DocTypes.") - + "
  2. " - - + "
  3. " - + __("These permissions will apply for all transactions where the permitted record is linked. For example, if Company C is added to User Permissions of user X, user X will only be able to see transactions that has company C as a linked value.") - + "
  4. " - - + "
  5. " - + __("These will also be set as default values for those links, if only one such permission record is defined.") - + "
  6. " - - + "
  7. " - + __("A user can be permitted to multiple records of the same DocType.") - + "
  8. \ -
").appendTo(page.main); - wrapper.user_permissions = new frappe.UserPermissions(wrapper); -} - -frappe.pages['user-permissions'].refresh = function(wrapper) { - wrapper.user_permissions.set_from_route(); -} - -frappe.UserPermissions = Class.extend({ - init: function(wrapper) { - this.wrapper = wrapper; - this.body = $(this.wrapper).find(".user-settings"); - this.filters = {}; - this.make(); - this.refresh(); - }, - make: function() { - var me = this; - - $(this.wrapper).find(".view-role-permissions").on("click", function() { - frappe.route_options = { doctype: me.get_doctype() || "" }; - frappe.set_route("permission-manager"); - }) - - return frappe.call({ - module:"frappe.core", - page:"user_permissions", - method: "get_users_and_links", - callback: function(r) { - me.options = r.message; - - me.filters.user = me.wrapper.page.add_field({ - fieldname: "user", - label: __("User"), - fieldtype: "Select", - options: ([__("Select User") + "..."].concat(r.message.users)).join("\n") - }); - - me.filters.doctype = me.wrapper.page.add_field({ - fieldname: "doctype", - label: __("DocType"), - fieldtype: "Select", - options: ([__("Select DocType") + "..."].concat(me.get_link_names())).join("\n") - }); - - me.filters.user_permission = me.wrapper.page.add_field({ - fieldname: "user_permission", - label: __("Name"), - fieldtype: "Link", - options: "[Select]" - }); - - if(frappe.user_roles.includes("System Manager")) { - me.download = me.wrapper.page.add_field({ - fieldname: "download", - label: __("Download"), - fieldtype: "Button", - icon: "fa fa-download" - }); - - me.upload = me.wrapper.page.add_field({ - fieldname: "upload", - label: __("Upload"), - fieldtype: "Button", - icon: "fa fa-upload" - }); - } - - // bind change event - $.each(me.filters, function(k, f) { - f.$input.on("change", function() { - me.refresh(); - }); - }); - - // change options in user_permission link - me.filters.doctype.$input.on("change", function() { - me.filters.user_permission.df.options = me.get_doctype(); - }); - - me.set_from_route(); - me.setup_download_upload(); - } - }); - }, - setup_download_upload: function() { - var me = this; - me.download.$input.on("click", function() { - window.location.href = frappe.urllib.get_base_url() - + "/api/method/frappe.core.page.user_permissions.user_permissions.get_user_permissions_csv"; - }); - - me.upload.$input.on("click", function() { - var d = new frappe.ui.Dialog({ - title: __("Upload User Permissions"), - fields: [ - { - fieldtype:"HTML", - options: '

    '+ - "
  1. "+__("Upload CSV file containing all user permissions in the same format as Download.")+"
  2. "+ - "
  3. "+__("Any existing permission will be deleted / overwritten.")+"
  4. "+ - '

    ' - }, - { - fieldtype:"Attach", fieldname:"attach", - } - ], - primary_action_label: __("Upload and Sync"), - primary_action: function() { - var filedata = d.fields_dict.attach.get_value(); - if(!filedata) { - frappe.msgprint(__("Please attach a file")); - return; - } - frappe.call({ - method:"frappe.core.page.user_permissions.user_permissions.import_user_permissions", - args: { - filedata: filedata - }, - callback: function(r) { - if(!r.exc) { - frappe.msgprint(__("Permissions Updated")); - d.hide(); - } - } - }); - } - }); - d.show(); - }) - }, - get_link_names: function() { - return this.options.link_fields; - }, - set_from_route: function() { - var me = this; - if(frappe.route_options && this.filters && !$.isEmptyObject(this.filters)) { - $.each(frappe.route_options, function(key, value) { - if(me.filters[key] && frappe.route_options[key]!=null) - me.set_filter(key, value); - }); - frappe.route_options = null; - } - this.refresh(); - }, - set_filter: function(key, value) { - this.filters[key].$input.val(value); - }, - get_user: function() { - var user = this.filters.user.$input.val(); - return user== __("Select User") + "..." ? null : user; - }, - get_doctype: function() { - var doctype = this.filters.doctype.$input.val(); - return doctype== __("Select DocType") + "..." ? null : doctype; - }, - get_user_permission: function() { - // autosuggest hack! - var user_permission = this.filters.user_permission.$input.val(); - return (user_permission === "%") ? null : user_permission; - }, - render: function(prop_list) { - var me = this; - this.body.empty(); - this.prop_list = prop_list; - if(!prop_list || !prop_list.length) { - this.add_message(__("No User Restrictions found.")); - } else { - this.show_user_permissions_table(); - } - this.show_add_user_permission(); - if(this.get_user() && this.get_doctype()) { - $('').appendTo(this.body.find(".btn-area")).on("click", function() { - frappe.route_options = {doctype: me.get_doctype(), user:me.get_user() }; - frappe.set_route("query-report/Permitted Documents For User"); - }); - } - }, - add_message: function(txt) { - $('

    ' + txt + '

    ').appendTo(this.body); - }, - refresh: function() { - var me = this; - if(!me.filters.user) { - this.body.html("

    "+__("Loading")+"...

    "); - return; - } - if(!me.get_user() && !me.get_doctype()) { - this.body.html("

    "+__("Select User or DocType to start.")+"

    "); - return; - } - // get permissions - return frappe.call({ - module: "frappe.core", - page: "user_permissions", - method: "get_permissions", - args: { - parent: me.get_user(), - defkey: me.get_doctype(), - defvalue: me.get_user_permission() - }, - callback: function(r) { - me.render(r.message); - } - }); - }, - show_user_permissions_table: function() { - var me = this; - this.table = $("\ - \ - \ -
    ").appendTo(this.body); - - $('

    ' - +__("These restrictions will apply for Document Types where 'Apply User Permissions' is checked for the permission rule and a field with this value is present.") - +'

    ').appendTo(this.body); - - $.each([[__("Allow User"), 150], [__("If Document Type"), 150], [__("Is"),150], ["", 50]], - function(i, col) { - $("") - .html(col[0]) - .css("width", col[1]+"px") - .appendTo(me.table.find("thead tr")); - }); - - - $.each(this.prop_list, function(i, d) { - var row = $("").appendTo(me.table.find("tbody")); - - $("").html('' - +d.parent+'').appendTo(row); - $("").html(d.defkey).appendTo(row); - $("").html(d.defvalue).appendTo(row); - - me.add_delete_button(row, d); - }); - - }, - add_delete_button: function(row, d) { - var me = this; - $("") - .appendTo($("").appendTo(row)) - .attr("data-name", d.name) - .attr("data-user", d.parent) - .attr("data-defkey", d.defkey) - .attr("data-defvalue", d.defvalue) - .click(function() { - return frappe.call({ - module: "frappe.core", - page: "user_permissions", - method: "remove", - args: { - name: $(this).attr("data-name"), - user: $(this).attr("data-user"), - defkey: $(this).attr("data-defkey"), - defvalue: $(this).attr("data-defvalue") - }, - callback: function(r) { - if(r.exc) { - frappe.msgprint(__("Did not remove")); - } else { - me.refresh(); - } - } - }) - }); - }, - - show_add_user_permission: function() { - var me = this; - $("") - .appendTo($('

    ').appendTo(this.body)) - .click(function() { - var d = new frappe.ui.Dialog({ - title: __("Add A New Restriction"), - fields: [ - {fieldtype:"Select", label:__("Allow User"), - options:me.options.users, reqd:1, fieldname:"user"}, - {fieldtype:"Select", label: __("If Document Type"), fieldname:"defkey", - options:me.get_link_names(), reqd:1}, - {fieldtype:"Link", label:__("Is"), fieldname:"defvalue", - options:'[Select]', reqd:1}, - {fieldtype:"Button", label: __("Add"), fieldname:"add"}, - ] - }); - if(me.get_user()) { - d.set_value("user", me.get_user()); - d.get_input("user").prop("disabled", true); - } - if(me.get_doctype()) { - d.set_value("defkey", me.get_doctype()); - d.get_input("defkey").prop("disabled", true); - } - if(me.get_user_permission()) { - d.set_value("defvalue", me.get_user_permission()); - d.get_input("defvalue").prop("disabled", true); - } - - d.fields_dict["defvalue"].get_query = function(txt) { - if(!d.get_value("defkey")) { - frappe.throw(__("Please select Document Type")); - } - - return { - doctype: d.get_value("defkey") - } - }; - - d.get_input("add").click(function() { - var args = d.get_values(); - if(!args) { - return; - } - frappe.call({ - module: "frappe.core", - page: "user_permissions", - method: "add", - args: args, - callback: function(r) { - if(r.exc) { - frappe.msgprint(__("Did not add")); - } else { - me.refresh(); - } - } - }) - d.hide(); - }); - d.show(); - }); - } -}) diff --git a/frappe/core/page/user_permissions/user_permissions.json b/frappe/core/page/user_permissions/user_permissions.json deleted file mode 100644 index ab831e1919..0000000000 --- a/frappe/core/page/user_permissions/user_permissions.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "content": null, - "creation": "2013-01-01 18:50:55", - "docstatus": 0, - "doctype": "Page", - "icon": "fa fa-user", - "idx": 1, - "modified": "2014-05-28 16:53:43.103533", - "modified_by": "Administrator", - "module": "Core", - "name": "user-permissions", - "owner": "Administrator", - "page_name": "user-permissions", - "roles": [], - "script": null, - "standard": "Yes", - "style": null, - "title": "User Permissions Manager" -} \ No newline at end of file diff --git a/frappe/core/page/user_permissions/user_permissions.py b/frappe/core/page/user_permissions/user_permissions.py deleted file mode 100644 index 98d7e90095..0000000000 --- a/frappe/core/page/user_permissions/user_permissions.py +++ /dev/null @@ -1,109 +0,0 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# MIT License. See license.txt - -from __future__ import unicode_literals -import frappe -from frappe import _ -import frappe.defaults -from frappe.permissions import (can_set_user_permissions, add_user_permission, - remove_user_permission, get_valid_perms) -from frappe.core.doctype.user.user import get_system_users -from frappe.utils.csvutils import UnicodeWriter, read_csv_content_from_uploaded_file -from frappe.defaults import clear_default - -@frappe.whitelist() -def get_users_and_links(): - return { - "users": get_system_users(), - "link_fields": get_doctypes_for_user_permissions() - } - -@frappe.whitelist() -def get_permissions(parent=None, defkey=None, defvalue=None): - if defkey and not can_set_user_permissions(defkey, defvalue): - raise frappe.PermissionError - - conditions, values = _build_conditions(locals()) - - permissions = frappe.db.sql("""select name, parent, defkey, defvalue - from tabDefaultValue - where parent not in ('__default', '__global') - and substr(defkey,1,1)!='_' - and parenttype='User Permission' - {conditions} - order by parent, defkey""".format(conditions=conditions), values, as_dict=True) - - if not defkey: - out = [] - doctypes = get_doctypes_for_user_permissions() - for p in permissions: - if p.defkey in doctypes: - out.append(p) - permissions = out - - return permissions - -def _build_conditions(filters): - conditions = [] - values = {} - for key, value in filters.items(): - if filters.get(key): - conditions.append("and `{key}`=%({key})s".format(key=key)) - values[key] = value - - return "\n".join(conditions), values - -@frappe.whitelist() -def remove(user, name, defkey, defvalue): - if not can_set_user_permissions(defkey, defvalue): - frappe.throw(_("Cannot remove permission for DocType: {0} and Name: {1}").format( - defkey, defvalue), frappe.PermissionError) - - remove_user_permission(defkey, defvalue, user, name) - -@frappe.whitelist() -def add(user, defkey, defvalue): - if not can_set_user_permissions(defkey, defvalue): - frappe.throw(_("Cannot set permission for DocType: {0} and Name: {1}").format( - defkey, defvalue), frappe.PermissionError) - - add_user_permission(defkey, defvalue, user, with_message=True) - -def get_doctypes_for_user_permissions(): - '''Get doctypes for the current user where user permissions are applicable''' - user_roles = frappe.get_roles() - - if "System Manager" in user_roles: - doctypes = set([p.parent for p in get_valid_perms()]) - else: - doctypes = set([p.parent for p in get_valid_perms() if p.set_user_permissions]) - - single_doctypes = set([d.name for d in frappe.get_all("DocType", {"issingle": 1})]) - - return sorted(doctypes.difference(single_doctypes)) - - -@frappe.whitelist() -def get_user_permissions_csv(): - out = [["User Permissions"], ["User", "Document Type", "Value"]] - out += [[a.parent, a.defkey, a.defvalue] for a in get_permissions()] - - csv = UnicodeWriter() - for row in out: - csv.writerow(row) - - frappe.response['result'] = str(csv.getvalue()) - frappe.response['type'] = 'csv' - frappe.response['doctype'] = "User Permissions" - -@frappe.whitelist() -def import_user_permissions(): - frappe.only_for("System Manager") - rows = read_csv_content_from_uploaded_file(ignore_encoding=True) - clear_default(parenttype="User Permission") - - if rows[0][0]!="User Permissions" and rows[1][0] != "User": - frappe.throw(frappe._("Please upload using the same template as download.")) - - for row in rows[2:]: - add_user_permission(row[1], row[2], row[0]) diff --git a/frappe/defaults.py b/frappe/defaults.py index b4c7ff6452..0e6e23ffdc 100644 --- a/frappe/defaults.py +++ b/frappe/defaults.py @@ -48,25 +48,10 @@ def is_a_user_permission_key(key): return ":" not in key and key != frappe.scrub(key) def get_user_permissions(user=None): - if not user: - user = frappe.session.user - - return build_user_permissions(user) - -def build_user_permissions(user): - out = frappe.cache().hget("user_permissions", user) - if out==None: - out = {} - for key, value in frappe.db.sql("""select defkey, ifnull(defvalue, '') as defvalue - from tabDefaultValue where parent=%s and parenttype='User Permission'""", (user,)): - out.setdefault(key, []).append(value) - - # add profile match - if user not in out.get("User", []): - out.setdefault("User", []).append(user) - - frappe.cache().hset("user_permissions", user, out) - return out + from frappe.core.doctype.user_permission.user_permission \ + import get_user_permissions as _get_user_permissions + '''Return frappe.core.doctype.user_permissions.user_permissions._get_user_permissions (kept for backward compatibility)''' + return _get_user_permissions(user) def get_defaults(user=None): globald = get_defaults_for() diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index 87e48b0450..ee7fffa242 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -70,7 +70,6 @@ def getdoctype(doctype, with_parent=False, cached_timestamp=None): if not docs: docs = get_meta_bundle(doctype) - frappe.response['user_permissions'] = get_user_permissions(docs) frappe.response['user_settings'] = get_user_settings(parent_dt or doctype) if cached_timestamp and docs[0].modified==cached_timestamp: @@ -102,16 +101,6 @@ def get_docinfo(doc=None, doctype=None, name=None): "rating": get_feedback_rating(doc.doctype, doc.name) } -def get_user_permissions(meta): - out = {} - all_user_permissions = frappe.defaults.get_user_permissions() - - for m in meta: - for df in m.get_fields_to_check_permissions(all_user_permissions): - out[df.options] = list(set(all_user_permissions[df.options])) - - return out - def get_attachments(dt, dn): return frappe.get_all("File", fields=["name", "file_name", "file_url", "is_private"], filters = {"attached_to_name": dn, "attached_to_doctype": dt}) diff --git a/frappe/model/create_new.py b/frappe/model/create_new.py index cc5c394d9f..846d8101e6 100644 --- a/frappe/model/create_new.py +++ b/frappe/model/create_new.py @@ -11,6 +11,7 @@ from frappe.utils import nowdate, nowtime, now_datetime import frappe.defaults from frappe.model.db_schema import type_map import copy +from frappe.core.doctype.user_permission.user_permission import get_user_permissions def get_new_doc(doctype, parent_doc = None, parentfield = None, as_dict=False): if doctype not in frappe.local.new_doc_templates: @@ -47,7 +48,7 @@ def make_new_doc(doctype): return doc def set_user_and_static_default_values(doc): - user_permissions = frappe.defaults.get_user_permissions() + user_permissions = get_user_permissions() defaults = frappe.defaults.get_defaults() for df in doc.meta.get("fields"): @@ -103,7 +104,7 @@ def get_static_default_value(df, user_permissions): def set_dynamic_default_values(doc, parent_doc, parentfield): # these values should not be cached - user_permissions = frappe.defaults.get_user_permissions() + user_permissions = get_user_permissions() for df in frappe.get_meta(doc["doctype"]).get("fields"): if df.get("default"): diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index eeee7f4bf7..56e4cc343d 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -388,7 +388,7 @@ class DatabaseQuery(object): # apply user permissions? if role_permissions.get("apply_user_permissions", {}).get("read"): # get user permissions - user_permissions = frappe.defaults.get_user_permissions(self.user) + user_permissions = frappe.permissions.get_user_permissions(self.user) self.add_user_permissions(user_permissions, user_permission_doctypes=role_permissions.get("user_permission_doctypes").get("read")) diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py index 3bf49a5f6a..e242a48c88 100644 --- a/frappe/model/delete_doc.py +++ b/frappe/model/delete_doc.py @@ -11,7 +11,7 @@ from frappe.utils.file_manager import remove_all from frappe.utils.password import delete_all_passwords_for from frappe import _ from frappe.model.naming import revert_series_if_last -from frappe.utils.global_search import delete_for_document +from frappe.utils.global_search import delete_for_document def delete_doc(doctype=None, name=None, force=0, ignore_doctypes=None, for_reload=False, ignore_permissions=False, flags=None, ignore_on_trash=False): @@ -158,8 +158,13 @@ def update_flags(doc, flags=None, ignore_permissions=False): def check_permission_and_not_submitted(doc): # permission - if not doc.flags.ignore_permissions and frappe.session.user!="Administrator" and (not doc.has_permission("delete") or (doc.doctype=="DocType" and not doc.custom)): - frappe.msgprint(_("User not allowed to delete {0}: {1}").format(doc.doctype, doc.name), raise_exception=True) + if (not doc.flags.ignore_permissions + and frappe.session.user!="Administrator" + and ( + not doc.has_permission("delete") + or (doc.doctype=="DocType" and not doc.custom))): + frappe.msgprint(_("User not allowed to delete {0}: {1}") + .format(doc.doctype, doc.name), raise_exception=frappe.PermissionError) # check if submitted if doc.docstatus == 1: diff --git a/frappe/patches.txt b/frappe/patches.txt index 8adf636701..3347ae8287 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -189,3 +189,4 @@ frappe.patches.v8_1.enable_allow_error_traceback_in_system_settings frappe.patches.v8_1.update_format_options_in_auto_email_report frappe.patches.v8_1.delete_custom_docperm_if_doctype_not_exists frappe.patches.v8_5.delete_email_group_member_with_invalid_emails +frappe.patches.v8_x.update_user_permission diff --git a/frappe/patches/v8_x/__init__.py b/frappe/patches/v8_x/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/patches/v8_x/update_user_permission.py b/frappe/patches/v8_x/update_user_permission.py new file mode 100644 index 0000000000..4ceb26e945 --- /dev/null +++ b/frappe/patches/v8_x/update_user_permission.py @@ -0,0 +1,25 @@ +import frappe + +def execute(): + frappe.reload_doc('core', 'doctype', 'user_permission') + frappe.delete_doc('core', 'page', 'user-permissions') + for perm in frappe.db.sql(""" + select + name, parent, defkey, defvalue + from + tabDefaultValue + where + parent not in ('__default', '__global') + and + substr(defkey,1,1)!='_' + and + parenttype='User Permission' + """, as_dict=True): + frappe.get_doc(dict( + doctype='User Permission', + user=perm.parent, + allow=perm.defkey, + for_value=perm.defvalue + )).insert(ignore_permissions = True) + + frappe.db.sql('delete from tabDefaultValue where parenttype="User Permission"') diff --git a/frappe/permissions.py b/frappe/permissions.py index 13da62d366..29f223d08e 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -7,7 +7,6 @@ import frappe, copy, json from frappe import _, msgprint from frappe.utils import cint import frappe.share - rights = ("read", "write", "create", "delete", "submit", "cancel", "amend", "print", "email", "report", "import", "export", "set_user_permissions", "share") @@ -25,6 +24,9 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None): """ if not user: user = frappe.session.user + if verbose: + print('--- Checking for {0} {1} ---'.format(doctype, doc.name if doc else '-')) + if frappe.is_table(doctype): if verbose: print("Table type, always true") return True @@ -40,7 +42,7 @@ def has_permission(doctype, ptype="read", doc=None, verbose=False, user=None): return False if user=="Administrator": - if verbose: print("Administrator") + if verbose: print("Allowing Administrator") return True def false_if_not_shared(): @@ -210,7 +212,10 @@ def get_role_permissions(meta, user=None, verbose=False): if p.user_permission_doctypes: # set user_permission_doctypes in perms - user_permission_doctypes = json.loads(p.user_permission_doctypes) + try: + user_permission_doctypes = json.loads(p.user_permission_doctypes) + except ValueError: + user_permission_doctypes = [] else: user_permission_doctypes = get_linked_doctypes(meta.name) @@ -247,8 +252,12 @@ def get_role_permissions(meta, user=None, verbose=False): return frappe.local.role_permissions[cache_key] +def get_user_permissions(user): + from frappe.core.doctype.user_permission.user_permission import get_user_permissions + return get_user_permissions(user) + def user_has_permission(doc, verbose=True, user=None, user_permission_doctypes=None): - from frappe.defaults import get_user_permissions + from frappe.core.doctype.user_permission.user_permission import get_user_permissions user_permissions = get_user_permissions(user) user_permission_doctypes = get_user_permission_doctypes(user_permission_doctypes, user_permissions) @@ -258,6 +267,10 @@ def user_has_permission(doc, verbose=True, user=None, user_permission_doctypes=N messages = {} + if not user_permission_doctypes: + # no doctypes restricted + end_result = True + # check multiple sets of user_permission_doctypes using OR condition for doctypes in user_permission_doctypes: result = True @@ -309,9 +322,9 @@ def has_controller_permissions(doc, ptype, user=None): def get_doctypes_with_read(): return list(set([p.parent for p in get_valid_perms()])) -def get_valid_perms(doctype=None): +def get_valid_perms(doctype=None, user=None): '''Get valid permissions for the current user from DocPerm and Custom DocPerm''' - roles = get_roles() + roles = get_roles(user) perms = get_perms_for(roles) custom_perms = get_perms_for(roles, 'Custom DocPerm') @@ -360,7 +373,8 @@ def get_roles(user=None, with_standard=True): def get_perms_for(roles, perm_doctype='DocPerm'): '''Get perms for given roles''' - return frappe.db.sql("""select * from `tab{doctype}` where docstatus=0 + return frappe.db.sql(""" + select * from `tab{doctype}` where docstatus=0 and ifnull(permlevel,0)=0 and role in ({roles})""".format(doctype = perm_doctype, roles=", ".join(["%s"]*len(roles))), tuple(roles), as_dict=1) @@ -386,22 +400,28 @@ def set_user_permission_if_allowed(doctype, name, user, with_message=False): if get_role_permissions(frappe.get_meta(doctype), user).set_user_permissions!=1: add_user_permission(doctype, name, user, with_message) -def add_user_permission(doctype, name, user, with_message=False): - '''Add user default''' - if name not in frappe.defaults.get_user_permissions(user).get(doctype, []): +def add_user_permission(doctype, name, user, apply=False): + '''Add user permission''' + from frappe.core.doctype.user_permission.user_permission import get_user_permissions + if name not in get_user_permissions(user).get(doctype, []): if not frappe.db.exists(doctype, name): frappe.throw(_("{0} {1} not found").format(_(doctype), name), frappe.DoesNotExistError) - frappe.defaults.add_default(doctype, name, user, "User Permission") - elif with_message: - msgprint(_("Permission already set")) + frappe.get_doc(dict( + doctype='User Permission', + user=user, + allow=doctype, + for_value=name, + apply_for_all_roles=apply + )).insert() -def remove_user_permission(doctype, name, user, default_value_name=None): - frappe.defaults.clear_default(key=doctype, value=name, parent=user, parenttype="User Permission", - name=default_value_name) +def remove_user_permission(doctype, name, user): + user_permission_name = frappe.db.get_value('User Permission', + dict(user=user, allow=doctype, for_value=name)) + frappe.delete_doc('User Permission', user_permission_name) def clear_user_permissions_for_doctype(doctype): - frappe.defaults.clear_default(parenttype="User Permission", key=doctype) + frappe.cache().delete_value('user_permissions') def can_import(doctype, raise_exception=False): if not ("System Manager" in frappe.get_roles() or has_permission(doctype, "import")): @@ -426,9 +446,10 @@ def apply_user_permissions(doctype, ptype, user=None): def get_user_permission_doctypes(user_permission_doctypes, user_permissions): """returns a list of list like [["User", "Blog Post"], ["User"]]""" - if cint(frappe.db.get_single_value("System Settings", "ignore_user_permissions_if_missing")): + if cint(frappe.get_system_settings('ignore_user_permissions_if_missing')): # select those user permission doctypes for which user permissions exist! - user_permission_doctypes = [list(set(doctypes).intersection(set(user_permissions.keys()))) + user_permission_doctypes = [ + list(set(doctypes).intersection(set(user_permissions.keys()))) for doctypes in user_permission_doctypes] if len(user_permission_doctypes) > 1: @@ -452,6 +473,22 @@ def get_user_permission_doctypes(user_permission_doctypes, user_permissions): return user_permission_doctypes +def update_permission_property(doctype, role, permlevel, ptype, value=None, validate=True): + '''Update a property in Custom Perm''' + from frappe.core.doctype.doctype.doctype import validate_permissions_for_doctype + out = setup_custom_perms(doctype) + + name = frappe.get_value('Custom DocPerm', dict(parent=doctype, role=role, + permlevel=permlevel)) + + frappe.db.sql(""" + update `tabCustom DocPerm` + set `{0}`=%s where name=%s""".format(ptype), (value, name)) + if validate: + validate_permissions_for_doctype(doctype) + + return out + def setup_custom_perms(parent): '''if custom permssions are not setup for the current doctype, set them up''' if not frappe.db.exists('Custom DocPerm', dict(parent=parent)): diff --git a/frappe/public/js/frappe/defaults.js b/frappe/public/js/frappe/defaults.js index 24ba5e630e..570ae61e2a 100644 --- a/frappe/public/js/frappe/defaults.js +++ b/frappe/public/js/frappe/defaults.js @@ -77,10 +77,6 @@ frappe.defaults = { }, get_user_permissions: function() { - return frappe.defaults.user_permissions; + return frappe.boot.user_permissions; }, - set_user_permissions: function(user_permissions) { - if(!user_permissions) return; - frappe.defaults.user_permissions = $.extend(frappe.defaults.user_permissions || {}, user_permissions); - } } diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 05facf346d..a7024a9fb7 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -599,9 +599,9 @@ frappe.views.ListView = frappe.ui.BaseList.extend({ }, true); } if (frappe.model.can_set_user_permissions(this.doctype)) { - this.page.add_menu_item(__('User Permissions Manager'), function () { - frappe.set_route('user-permissions', { - doctype: me.doctype + this.page.add_menu_item(__('User Permissions'), function () { + frappe.set_route('List', 'User Permission', { + allow: me.doctype }); }, true); } diff --git a/frappe/public/js/frappe/model/create_new.js b/frappe/public/js/frappe/model/create_new.js index 04496c6238..a25c3e70ca 100644 --- a/frappe/public/js/frappe/model/create_new.js +++ b/frappe/public/js/frappe/model/create_new.js @@ -127,8 +127,10 @@ $.extend(frappe.model, { var user_default = ""; var user_permissions = frappe.defaults.get_user_permissions(); var meta = frappe.get_meta(doc.doctype); - var has_user_permissions = (df.fieldtype==="Link" && user_permissions - && df.ignore_user_permissions != 1 && user_permissions[df.options]); + var has_user_permissions = (df.fieldtype==="Link" + && user_permissions + && df.ignore_user_permissions != 1 + && user_permissions[df.options]); // don't set defaults for "User" link field using User Permissions! if (df.fieldtype==="Link" && df.options!=="User") { diff --git a/frappe/public/js/frappe/model/model.js b/frappe/public/js/frappe/model/model.js index adb2e845a6..7b22c5fd47 100644 --- a/frappe/public/js/frappe/model/model.js +++ b/frappe/public/js/frappe/model/model.js @@ -112,7 +112,6 @@ $.extend(frappe.model, { localStorage["_doctype:" + doctype] = JSON.stringify(r.docs); } frappe.model.init_doctype(doctype); - frappe.defaults.set_user_permissions(r.user_permissions); if(r.user_settings) { // remember filters and other settings from last view diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index b3762fea6f..7e0bc14a13 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -103,7 +103,7 @@ frappe.views.QueryReport = Class.extend({ doctype: "Report", name: me.report_name }; - frappe.set_route("user-permissions"); + frappe.set_route('List', 'User Permission'); }, true); } diff --git a/frappe/public/js/frappe/views/reports/reportview.js b/frappe/public/js/frappe/views/reports/reportview.js index 93d6a1f1aa..b7fd287b02 100644 --- a/frappe/public/js/frappe/views/reports/reportview.js +++ b/frappe/public/js/frappe/views/reports/reportview.js @@ -821,12 +821,12 @@ frappe.views.ReportView = frappe.ui.BaseList.extend({ make_user_permissions: function() { var me = this; if(this.docname && frappe.model.can_set_user_permissions("Report")) { - this.page.add_menu_item(__("User Permissions Manager"), function() { + this.page.add_menu_item(__("User Permissions"), function() { frappe.route_options = { doctype: "Report", name: me.docname }; - frappe.set_route("user-permissions"); + frappe.set_route('List', 'User Permission'); }, true); } }, diff --git a/frappe/test_runner.py b/frappe/test_runner.py index a50febabf3..c4fa2f6f47 100644 --- a/frappe/test_runner.py +++ b/frappe/test_runner.py @@ -266,12 +266,12 @@ def make_test_records_for_doctype(doctype, verbose=0, force=False): frappe.local.test_objects[doctype] += test_module._make_test_records(verbose) elif hasattr(test_module, "test_records"): - frappe.local.test_objects[doctype] += make_test_objects(doctype, test_module.test_records, verbose) + frappe.local.test_objects[doctype] += make_test_objects(doctype, test_module.test_records, verbose, force) else: test_records = frappe.get_test_records(doctype) if test_records: - frappe.local.test_objects[doctype] += make_test_objects(doctype, test_records, verbose) + frappe.local.test_objects[doctype] += make_test_objects(doctype, test_records, verbose, force) elif verbose: print_mandatory_fields(doctype) diff --git a/frappe/tests/test_permissions.py b/frappe/tests/test_permissions.py index 6ad0e84f07..d54c484eba 100644 --- a/frappe/tests/test_permissions.py +++ b/frappe/tests/test_permissions.py @@ -9,9 +9,11 @@ import frappe.defaults import unittest import json import frappe.model.meta -from frappe.core.page.user_permissions.user_permissions import add, remove, get_permissions -from frappe.permissions import clear_user_permissions_for_doctype, get_doc_permissions +from frappe.permissions import (add_user_permission, remove_user_permission, + clear_user_permissions_for_doctype, get_doc_permissions, add_permission, + get_valid_perms) from frappe.core.page.permission_manager.permission_manager import update, reset +from frappe.test_runner import make_test_records_for_doctype test_records = frappe.get_test_records('Blog Post') @@ -24,6 +26,7 @@ class TestPermissions(unittest.TestCase): user = frappe.get_doc("User", "test1@example.com") user.add_roles("Website Manager") + user.add_roles("System Manager") user = frappe.get_doc("User", "test2@example.com") user.add_roles("Blogger") @@ -36,6 +39,8 @@ class TestPermissions(unittest.TestCase): reset('Contact') reset('Salutation') + frappe.db.sql('delete from `tabUser Permission`') + self.set_ignore_user_permissions_if_missing(0) frappe.set_user("test1@example.com") @@ -78,7 +83,7 @@ class TestPermissions(unittest.TestCase): def test_user_permissions_in_doc(self): self.set_user_permission_doctypes(["Blog Category"]) - frappe.permissions.add_user_permission("Blog Category", "_Test Blog Category 1", + add_user_permission("Blog Category", "_Test Blog Category 1", "test2@example.com") frappe.set_user("test2@example.com") @@ -94,7 +99,7 @@ class TestPermissions(unittest.TestCase): def test_user_permissions_in_report(self): self.set_user_permission_doctypes(["Blog Category"]) - frappe.permissions.add_user_permission("Blog Category", "_Test Blog Category 1", "test2@example.com") + add_user_permission("Blog Category", "_Test Blog Category 1", "test2@example.com") frappe.set_user("test2@example.com") names = [d.name for d in frappe.get_list("Blog Post", fields=["name", "blog_category"])] @@ -103,7 +108,7 @@ class TestPermissions(unittest.TestCase): self.assertFalse("-test-blog-post" in names) def test_default_values(self): - frappe.permissions.add_user_permission("Blog Category", "_Test Blog Category 1", "test2@example.com") + add_user_permission("Blog Category", "_Test Blog Category 1", "test2@example.com") frappe.set_user("test2@example.com") doc = frappe.new_doc("Blog Post") @@ -139,14 +144,14 @@ class TestPermissions(unittest.TestCase): def test_set_user_permissions(self): frappe.set_user("test1@example.com") - add("test2@example.com", "Blog Post", "-test-blog-post") + add_user_permission("Blog Post", "-test-blog-post", "test2@example.com") def test_not_allowed_to_set_user_permissions(self): frappe.set_user("test2@example.com") # this user can't add user permissions - self.assertRaises(frappe.PermissionError, add, - "test2@example.com", "Blog Post", "-test-blog-post") + self.assertRaises(frappe.PermissionError, add_user_permission, + "Blog Post", "-test-blog-post", "test2@example.com") def test_read_if_explicit_user_permissions_are_set(self): self.set_user_permission_doctypes(["Blog Post"]) @@ -165,13 +170,12 @@ class TestPermissions(unittest.TestCase): def test_not_allowed_to_remove_user_permissions(self): self.test_set_user_permissions() - defname = get_permissions("test2@example.com", "Blog Post", "-test-blog-post")[0].name frappe.set_user("test2@example.com") # user cannot remove their own user permissions - self.assertRaises(frappe.PermissionError, remove, - "test2@example.com", defname, "Blog Post", "-test-blog-post") + self.assertRaises(frappe.PermissionError, remove_user_permission, + "Blog Post", "-test-blog-post", "test2@example.com") def test_user_permissions_based_on_blogger(self): frappe.set_user("test2@example.com") @@ -181,7 +185,7 @@ class TestPermissions(unittest.TestCase): self.set_user_permission_doctypes(["Blog Post"]) frappe.set_user("test1@example.com") - add("test2@example.com", "Blog Post", "-test-blog-post") + add_user_permission("Blog Post", "-test-blog-post", "test2@example.com") frappe.set_user("test2@example.com") doc = frappe.get_doc("Blog Post", "-test-blog-post-1") @@ -199,9 +203,9 @@ class TestPermissions(unittest.TestCase): blog_post.get_field("title").set_only_once = 0 def test_user_permission_doctypes(self): - frappe.permissions.add_user_permission("Blog Category", "_Test Blog Category 1", + add_user_permission("Blog Category", "_Test Blog Category 1", "test2@example.com") - frappe.permissions.add_user_permission("Blogger", "_Test Blogger 1", + add_user_permission("Blogger", "_Test Blogger 1", "test2@example.com") frappe.set_user("test2@example.com") @@ -221,9 +225,9 @@ class TestPermissions(unittest.TestCase): def if_owner_setup(self): update('Blog Post', 'Blogger', 0, 'if_owner', 1) - frappe.permissions.add_user_permission("Blog Category", "_Test Blog Category 1", + add_user_permission("Blog Category", "_Test Blog Category 1", "test2@example.com") - frappe.permissions.add_user_permission("Blogger", "_Test Blogger 1", + add_user_permission("Blogger", "_Test Blogger 1", "test2@example.com") update('Blog Post', 'Blogger', 0, 'user_permission_doctypes', json.dumps(["Blog Category"])) @@ -231,11 +235,13 @@ class TestPermissions(unittest.TestCase): frappe.model.meta.clear_cache("Blog Post") def set_user_permission_doctypes(self, user_permission_doctypes): - set_user_permission_doctypes(doctype="Blog Post", role="Blogger", + set_user_permission_doctypes(["Blog Post"], role="Blogger", apply_user_permissions=1, user_permission_doctypes=user_permission_doctypes) def test_insert_if_owner_with_user_permissions(self): """If `If Owner` is checked for a Role, check if that document is allowed to be read, updated, submitted, etc. except be created, even if the document is restricted based on User Permissions.""" + frappe.delete_doc('Blog Post', '-test-blog-post-title') + self.set_user_permission_doctypes(["Blog Category"]) self.if_owner_setup() @@ -252,7 +258,7 @@ class TestPermissions(unittest.TestCase): self.assertRaises(frappe.PermissionError, doc.insert) frappe.set_user("Administrator") - frappe.permissions.add_user_permission("Blog Category", "_Test Blog Category", + add_user_permission("Blog Category", "_Test Blog Category", "test2@example.com") frappe.set_user("test2@example.com") @@ -273,8 +279,8 @@ class TestPermissions(unittest.TestCase): self.set_user_permission_doctypes(['Blog Category', 'Blog Post', 'Blogger']) frappe.set_user("Administrator") - frappe.permissions.add_user_permission("Blog Category", "_Test Blog Category", - "test2@example.com") + # add_user_permission("Blog Category", "_Test Blog Category", + # "test2@example.com") frappe.set_user("test2@example.com") doc = frappe.get_doc({ @@ -294,33 +300,73 @@ class TestPermissions(unittest.TestCase): self.assertTrue(doc.has_permission("write")) def test_strict_user_permissions(self): - """If `Strict User Permissions` is checked in System Settings, show records even if User Permissions are missing for a linked doctype""" - set_user_permission_doctypes(doctype="Contact", role="Sales User", + """If `Strict User Permissions` is checked in System Settings, + show records even if User Permissions are missing for a linked + doctype""" + + frappe.set_user("Administrator") + frappe.db.sql('delete from tabContact') + make_test_records_for_doctype('Contact', force=True) + + set_user_permission_doctypes("Contact", role="Sales User", apply_user_permissions=1, user_permission_doctypes=['Salutation']) - set_user_permission_doctypes(doctype="Salutation", role="All", + set_user_permission_doctypes("Salutation", role="All", apply_user_permissions=1, user_permission_doctypes=['Salutation']) - frappe.set_user("Administrator") - frappe.permissions.add_user_permission("Salutation", "Mr", "test3@example.com") + add_user_permission("Salutation", "Mr", "test3@example.com") self.set_strict_user_permissions(0) frappe.set_user("test3@example.com") - self.assertEquals(len(frappe.get_list("Contact")),2) + self.assertEquals(len(frappe.get_list("Contact")), 2) frappe.set_user("Administrator") self.set_strict_user_permissions(1) frappe.set_user("test3@example.com") - self.assertTrue(len(frappe.get_list("Contact")),1) + self.assertTrue(len(frappe.get_list("Contact")), 1) frappe.set_user("Administrator") self.set_strict_user_permissions(0) + def test_automatic_apply_user_permissions(self): + '''Test user permissions are automatically applied when a user permission + is created''' + # create a user + frappe.get_doc(dict(doctype='User', email='test_user_perm@example.com', + first_name='tester')).insert(ignore_if_duplicate=True) + frappe.get_doc(dict(doctype='Role', role_name='Test Role User Perm') + ).insert(ignore_if_duplicate=True) + + # add a permission for event + add_permission('DocType', 'Test Role User Perm') + frappe.get_doc('User', 'test_user_perm@example.com').add_roles('Test Role User Perm') -def set_user_permission_doctypes(doctype, role, apply_user_permissions, user_permission_doctypes): + + # add user permission + add_user_permission('Module Def', 'Core', 'test_user_perm@example.com', True) + + # check if user permission is applied in the new role + _perm = None + for perm in get_valid_perms('DocType', 'test_user_perm@example.com'): + if perm.role == 'Test Role User Perm': + _perm = perm + + self.assertEqual(_perm.apply_user_permissions, 1) + + # restrict by module + self.assertTrue('Module Def' in json.loads(_perm.user_permission_doctypes)) + + +def set_user_permission_doctypes(doctypes, role, apply_user_permissions, + user_permission_doctypes): user_permission_doctypes = None if not user_permission_doctypes else json.dumps(user_permission_doctypes) - update(doctype, role, 0, 'apply_user_permissions', 1) - update(doctype, role, 0, 'user_permission_doctypes', user_permission_doctypes) + if isinstance(doctypes, basestring): + doctypes = [doctypes] + + for doctype in doctypes: + update(doctype, role, 0, 'apply_user_permissions', 1) + update(doctype, role, 0, 'user_permission_doctypes', + user_permission_doctypes) - frappe.clear_cache(doctype=doctype) + frappe.clear_cache(doctype=doctype)