浏览代码

[refactor] user permissions (#3713)

* [refactor] user permissions

* [fix] tests

* [ux] user-permissions

* [minor] cleanup system settings

* [minor] end progress
version-14
Rushabh Mehta 8 年前
committed by Makarand Bauskar
父节点
当前提交
f609a478ae
共有 36 个文件被更改,包括 621 次插入664 次删除
  1. +2
    -0
      frappe/boot.py
  2. +1
    -2
      frappe/core/doctype/communication/feed.py
  3. +71
    -41
      frappe/core/doctype/system_settings/system_settings.json
  4. +1
    -0
      frappe/core/doctype/system_settings/system_settings.py
  5. +23
    -0
      frappe/core/doctype/system_settings/test_system_settings.js
  6. +5
    -1
      frappe/core/doctype/user/user.js
  7. +0
    -0
      frappe/core/doctype/user_permission/__init__.py
  8. +23
    -0
      frappe/core/doctype/user_permission/test_user_permission.js
  9. +10
    -0
      frappe/core/doctype/user_permission/test_user_permission.py
  10. +10
    -0
      frappe/core/doctype/user_permission/user_permission.js
  11. +188
    -0
      frappe/core/doctype/user_permission/user_permission.json
  12. +80
    -0
      frappe/core/doctype/user_permission/user_permission.py
  13. +16
    -8
      frappe/core/page/permission_manager/permission_manager.js
  14. +3
    -13
      frappe/core/page/permission_manager/permission_manager.py
  15. +0
    -1
      frappe/core/page/user_permissions/README.md
  16. +0
    -3
      frappe/core/page/user_permissions/__init__.py
  17. +0
    -365
      frappe/core/page/user_permissions/user_permissions.js
  18. +0
    -19
      frappe/core/page/user_permissions/user_permissions.json
  19. +0
    -109
      frappe/core/page/user_permissions/user_permissions.py
  20. +4
    -19
      frappe/defaults.py
  21. +0
    -11
      frappe/desk/form/load.py
  22. +3
    -2
      frappe/model/create_new.py
  23. +1
    -1
      frappe/model/db_query.py
  24. +8
    -3
      frappe/model/delete_doc.py
  25. +1
    -0
      frappe/patches.txt
  26. +0
    -0
      frappe/patches/v8_x/__init__.py
  27. +25
    -0
      frappe/patches/v8_x/update_user_permission.py
  28. +56
    -19
      frappe/permissions.py
  29. +1
    -5
      frappe/public/js/frappe/defaults.js
  30. +3
    -3
      frappe/public/js/frappe/list/list_view.js
  31. +4
    -2
      frappe/public/js/frappe/model/create_new.js
  32. +0
    -1
      frappe/public/js/frappe/model/model.js
  33. +1
    -1
      frappe/public/js/frappe/views/reports/query_report.js
  34. +2
    -2
      frappe/public/js/frappe/views/reports/reportview.js
  35. +2
    -2
      frappe/test_runner.py
  36. +77
    -31
      frappe/tests/test_permissions.py

+ 2
- 0
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':


+ 1
- 2
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


+ 71
- 41
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
}
}

+ 1
- 0
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():


+ 23
- 0
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()
]);

});

+ 5
- 1
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);
}



+ 0
- 0
frappe/core/doctype/user_permission/__init__.py 查看文件


+ 23
- 0
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()
]);

});

+ 10
- 0
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

+ 10
- 0
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}));
}
});

+ 188
- 0
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
}

+ 80
- 0
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

+ 16
- 8
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("<p class='text-muted'>" + __("Loading") + "...</div>");
this.body.html("<p class='text-muted'>" + __("Loading") + "...</p>");
return;
}
if(!me.get_doctype() && !me.get_role()) {
this.body.html("<p class='text-muted'>"+__("Select Document Type or Role to start.")+"</div>");
this.body.html("<p class='text-muted'>"+__("Select Document Type or Role to start.")+"</p>");
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('<ul class="user-permission-help small hidden" style="margin-left: -10px;">\
<li style="margin-top: 7px;"><a class="show-user-permission-doctypes grey">{%= __("Select Document Types") %}</a></li>\
<li style="margin-top: 3px;"><a class="show-user-permissions grey">{%= __("Show User Permissions") %}</a></li>\
</ul>', {});
d.help = `<ul class="user-permission-help small hidden"
style="margin-left: -10px;">
<li style="margin-top: 7px;"><a class="show-user-permission-doctypes">
${__("Select Document Types")}</a></li>
<li style="margin-top: 3px;"><a class="show-user-permissions">
${__("Show User Permissions")}</a></li>
</ul>`;

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() {


+ 3
- 13
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):


+ 0
- 1
frappe/core/page/user_permissions/README.md 查看文件

@@ -1 +0,0 @@
Interface to set user defaults (DefaultValue).

+ 0
- 3
frappe/core/page/user_permissions/__init__.py 查看文件

@@ -1,3 +0,0 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt


+ 0
- 365
frappe/core/page/user_permissions/user_permissions.js 查看文件

@@ -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");

$("<div class='user-settings' \
style='min-height: 200px; padding: 15px;'></div>\
<p style='padding: 15px; padding-bottom: 0px;'>\
<a class='view-role-permissions grey'>" + __("Edit Role Permissions") + "</a>\
</p><hr><div style='padding: 0px 15px;'>\
<h4>"+__("Help for User Permissions")+":</h4>\
<ol>\
<li>"
+ __("Apart from Role based Permission Rules, you can apply User Permissions based on DocTypes.")
+ "</li>"

+ "<li>"
+ __("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.")
+ "</li>"

+ "<li>"
+ __("These will also be set as default values for those links, if only one such permission record is defined.")
+ "</li>"

+ "<li>"
+ __("A user can be permitted to multiple records of the same DocType.")
+ "</li>\
</ol></div>").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: '<p class="text-muted"><ol>'+
"<li>"+__("Upload CSV file containing all user permissions in the same format as Download.")+"</li>"+
"<li><strong>"+__("Any existing permission will be deleted / overwritten.")+"</strong></li>"+
'</p>'
},
{
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()) {
$('<button class="btn btn-default btn-sm" style="margin-left: 10px;">\
Show Allowed Documents</button>').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) {
$('<p class="text-muted">' + txt + '</p>').appendTo(this.body);
},
refresh: function() {
var me = this;
if(!me.filters.user) {
this.body.html("<p class='text-muted'>"+__("Loading")+"...</p>");
return;
}
if(!me.get_user() && !me.get_doctype()) {
this.body.html("<p class='text-muted'>"+__("Select User or DocType to start.")+"</p>");
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 = $("<table class='table table-bordered'>\
<thead><tr></tr></thead>\
<tbody></tbody>\
</table>").appendTo(this.body);

$('<p class="text-muted small">'
+__("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.")
+'</p>').appendTo(this.body);

$.each([[__("Allow User"), 150], [__("If Document Type"), 150], [__("Is"),150], ["", 50]],
function(i, col) {
$("<th>")
.html(col[0])
.css("width", col[1]+"px")
.appendTo(me.table.find("thead tr"));
});


$.each(this.prop_list, function(i, d) {
var row = $("<tr>").appendTo(me.table.find("tbody"));

$("<td>").html('<a class="grey" href="#Form/User/'+encodeURIComponent(d.parent)+'">'
+d.parent+'</a>').appendTo(row);
$("<td>").html(d.defkey).appendTo(row);
$("<td>").html(d.defvalue).appendTo(row);

me.add_delete_button(row, d);
});

},
add_delete_button: function(row, d) {
var me = this;
$("<button class='btn btn-sm btn-default'><i class='fa fa-remove'></i></button>")
.appendTo($("<td>").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;
$("<button class='btn btn-default btn-sm'>"+__("Add A User Restriction")+"</button>")
.appendTo($('<p class="btn-area">').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();
});
}
})

+ 0
- 19
frappe/core/page/user_permissions/user_permissions.json 查看文件

@@ -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"
}

+ 0
- 109
frappe/core/page/user_permissions/user_permissions.py 查看文件

@@ -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])

+ 4
- 19
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()


+ 0
- 11
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})


+ 3
- 2
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"):


+ 1
- 1
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"))



+ 8
- 3
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:


+ 1
- 0
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

+ 0
- 0
frappe/patches/v8_x/__init__.py 查看文件


+ 25
- 0
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"')

+ 56
- 19
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)):


+ 1
- 5
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);
}
}

+ 3
- 3
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);
}


+ 4
- 2
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") {


+ 0
- 1
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


+ 1
- 1
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);
}



+ 2
- 2
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);
}
},


+ 2
- 2
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)


+ 77
- 31
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)

正在加载...
取消
保存