diff --git a/frappe/core/doctype/user_permission/user_permission.py b/frappe/core/doctype/user_permission/user_permission.py index 13fdc3c5b1..4d6d4e1b08 100644 --- a/frappe/core/doctype/user_permission/user_permission.py +++ b/frappe/core/doctype/user_permission/user_permission.py @@ -27,7 +27,13 @@ def get_user_permissions(user=None): 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) + meta = frappe.get_meta(perm.allow) + if not perm.allow in out: + out[perm.allow] = [] + out[perm.allow].append(perm.for_value) + + if meta.is_nested_set(): + out[perm.allow].extend(frappe.db.get_descendants(perm.allow, perm.for_value)) frappe.cache().hset("user_permissions", user, out) except frappe.SQLError as e: diff --git a/frappe/database.py b/frappe/database.py index be696dadf3..f313769994 100644 --- a/frappe/database.py +++ b/frappe/database.py @@ -948,6 +948,12 @@ class Database: return s + def get_descendants(self, doctype, name): + '''Return descendants of the current record''' + lft, rgt = self.get_value(doctype, name, ('lft', 'rgt')) + return self.sql_list('''select name from `tab{doctype}` + where lft > {lft} and rgt < {rgt}'''.format(doctype=doctype, lft=lft, rgt=rgt)) + def enqueue_jobs_after_commit(): if frappe.flags.enqueue_after_commit and len(frappe.flags.enqueue_after_commit) > 0: for job in frappe.flags.enqueue_after_commit: diff --git a/frappe/model/meta.py b/frappe/model/meta.py index b088822aec..49093edda2 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -403,6 +403,9 @@ class Meta(Document): module_name = module_name, doctype_name = doctype, suffix=suffix) return None + def is_nested_set(self): + return self.has_field('lft') and self.has_field('rgt') + doctype_table_fields = [ frappe._dict({"fieldname": "fields", "options": "DocField"}), frappe._dict({"fieldname": "permissions", "options": "DocPerm"}) diff --git a/frappe/tests/test_db_query.py b/frappe/tests/test_db_query.py index 464e89726b..8f8126cd76 100644 --- a/frappe/tests/test_db_query.py +++ b/frappe/tests/test_db_query.py @@ -6,6 +6,7 @@ import frappe, unittest from frappe.model.db_query import DatabaseQuery from frappe.desk.reportview import get_filters_cond +from frappe.permissions import add_user_permission, clear_user_permissions_for_doctype class TestReportview(unittest.TestCase): def test_basic(self): @@ -133,6 +134,40 @@ class TestReportview(unittest.TestCase): "datediff(modified, creation) as date_diff"], limit_start=0, limit_page_length=1) self.assertTrue('date_diff' in data[0]) + def test_nested_permission(self): + clear_user_permissions_for_doctype("File") + delete_test_file_hierarchy() # delete already existing folders + from frappe.core.doctype.file.file import create_new_folder + frappe.set_user('Administrator') + + create_new_folder('level1-A', 'Home') + create_new_folder('level2-A', 'Home/level1-A') + create_new_folder('level2-B', 'Home/level1-A') + create_new_folder('level3-A', 'Home/level1-A/level2-A') + + create_new_folder('level1-B', 'Home') + create_new_folder('level2-A', 'Home/level1-B') + + # user permission for only one root folder + add_user_permission('File', 'Home/level1-A', 'test2@example.com') + + from frappe.core.page.permission_manager.permission_manager import update + update('File', 'All', 0, 'if_owner', 0) # to avoid if_owner filter + + frappe.set_user('test2@example.com') + data = DatabaseQuery("File").execute() + + # children of root folder (for which we added user permission) should be accessible + self.assertTrue({"name": "Home/level1-A/level2-A"} in data) + self.assertTrue({"name": "Home/level1-A/level2-B"} in data) + self.assertTrue({"name": "Home/level1-A/level2-A/level3-A"} in data) + + # other folders should not be accessible + self.assertFalse({"name": "Home/level1-B"} in data) + self.assertFalse({"name": "Home/level1-B/level2-B"} in data) + update('File', 'All', 0, 'if_owner', 1) + frappe.set_user('Administrator') + def create_event(subject="_Test Event", starts_on=None): """ create a test event """ @@ -145,4 +180,16 @@ def create_event(subject="_Test Event", starts_on=None): "starts_on": get_datetime(starts_on), }).insert(ignore_permissions=True) - return event \ No newline at end of file + return event + +def delete_test_file_hierarchy(): + files_to_delete = [ + 'Home/level1-A/level2-A/level3-A', + 'Home/level1-A/level2-A', + 'Home/level1-A/level2-B', + 'Home/level1-A', + 'Home/level1-B/level2-A', + 'Home/level1-B' + ] + for file_name in files_to_delete: + frappe.delete_doc('File', file_name)