From 97ea7f91e2f6cb1b3ca9e9458e0488e8092c0549 Mon Sep 17 00:00:00 2001 From: Anand Doshi Date: Tue, 17 Dec 2013 19:03:30 +0530 Subject: [PATCH] Added import, export, print, email and restrict permissions --- core/doctype/docperm/docperm.txt | 32 ++++- core/doctype/doctype/doctype.py | 32 ++++- core/doctype/doctype/doctype.txt | 24 +--- .../page/data_import_tool/data_import_tool.py | 6 +- .../permission_manager/permission_manager.js | 82 ++++++----- core/page/user_properties/user_properties.js | 2 +- core/page/user_properties/user_properties.py | 46 +++++-- public/js/wn/form/infobar.js | 2 +- public/js/wn/misc/user.js | 4 - public/js/wn/model/model.js | 30 +++- public/js/wn/views/query_report.js | 2 +- public/js/wn/views/reportview.js | 2 +- webnotes/__init__.py | 91 ++---------- webnotes/install_lib/install.py | 1 - webnotes/model/bean.py | 3 +- webnotes/permissions.py | 129 ++++++++++++++++++ webnotes/profile.py | 31 +++-- webnotes/widgets/reportview.py | 3 +- website/doctype/blog_post/test_blog_post.py | 37 ++++- 19 files changed, 371 insertions(+), 188 deletions(-) create mode 100644 webnotes/permissions.py diff --git a/core/doctype/docperm/docperm.txt b/core/doctype/docperm/docperm.txt index 634fb7c1b2..fabc9ae904 100644 --- a/core/doctype/docperm/docperm.txt +++ b/core/doctype/docperm/docperm.txt @@ -2,7 +2,7 @@ { "creation": "2013-02-22 01:27:33", "docstatus": 0, - "modified": "2013-07-22 17:01:58", + "modified": "2013-12-17 12:29:13", "modified_by": "Administrator", "owner": "Administrator" }, @@ -150,6 +150,36 @@ "print_width": "32px", "width": "32px" }, + { + "doctype": "DocField", + "fieldname": "export", + "fieldtype": "Check", + "label": "Export" + }, + { + "doctype": "DocField", + "fieldname": "import", + "fieldtype": "Check", + "label": "Import" + }, + { + "doctype": "DocField", + "fieldname": "print", + "fieldtype": "Check", + "label": "Print" + }, + { + "doctype": "DocField", + "fieldname": "email", + "fieldtype": "Check", + "label": "Email" + }, + { + "doctype": "DocField", + "fieldname": "restrict", + "fieldtype": "Check", + "label": "Restrict" + }, { "doctype": "DocField", "fieldname": "match", diff --git a/core/doctype/doctype/doctype.py b/core/doctype/doctype/doctype.py index 8c4edf6fe2..bb34bba112 100644 --- a/core/doctype/doctype/doctype.py +++ b/core/doctype/doctype/doctype.py @@ -229,9 +229,10 @@ def validate_permissions(permissions, for_remove=False): if doctype: issingle = cint(webnotes.conn.get_value("DocType", doctype, "issingle")) issubmittable = cint(webnotes.conn.get_value("DocType", doctype, "is_submittable")) + isimportable = cint(webnotes.conn.get_value("DocType", doctype, "allow_import")) def get_txt(d): - return "For %s (level %s) in %s row %s:" % (d.role, d.permlevel, d.parent, d.idx) + return "For %s (level %s) in %s, row #%s:" % (d.role, d.permlevel, d.parent, d.idx) def check_atleast_one_set(d): if not d.read and not d.write and not d.submit and not d.cancel and not d.create: @@ -269,10 +270,25 @@ def validate_permissions(permissions, for_remove=False): if d.amend and not d.write: webnotes.msgprint(get_txt(d) + " Cannot set Amend if Cancel is not set.", raise_exception=True) + if (d.fields.get("import") or d.export) and not d.report: + webnotes.msgprint(get_txt(d) + " Cannot set Import or Export permission if Report is not set.", + raise_exception=True) - def remove_report_if_single(d): - if d.report and issingle: - webnotes.msgprint(doctype + " is a single DocType, permission of type Report is meaningless.") + def remove_rights_for_single(d): + if not issingle: + return + + if d.report: + webnotes.msgprint("{doctype} {meaningless}".format(doctype=doctype, + meaningless=_("is a single DocType, permission of type Report is meaningless."))) + d.report = 0 + d.fields["import"] = 0 + d.fields["export"] = 0 + + if d.restrict: + webnotes.msgprint("{doctype} {meaningless}".format(doctype=doctype, + meaningless=_("is a single DocType, permission of type Restrict is meaningless."))) + d.restrict = 0 def check_if_submittable(d): if d.submit and not issubmittable: @@ -282,6 +298,11 @@ def validate_permissions(permissions, for_remove=False): webnotes.msgprint(doctype + " is not Submittable, cannot assign amend rights.", raise_exception=True) + def check_if_importable(d): + if d.fields.get("import") and not isimportable: + webnotes.throw("{doctype}: {not_importable}".format(doctype=doctype, + not_importable=_("is not allowed to be imported, cannot assign import rights."))) + for d in permissions: if not d.permlevel: d.permlevel=0 @@ -290,8 +311,9 @@ def validate_permissions(permissions, for_remove=False): check_double(d) check_permission_dependency(d) check_if_submittable(d) + check_if_importable(d) check_level_zero_is_set(d) - remove_report_if_single(d) + remove_rights_for_single(d) def make_module_and_roles(doclist, perm_doctype="DocPerm"): try: diff --git a/core/doctype/doctype/doctype.txt b/core/doctype/doctype/doctype.txt index eb4674c741..215b73c271 100644 --- a/core/doctype/doctype/doctype.txt +++ b/core/doctype/doctype/doctype.txt @@ -2,7 +2,7 @@ { "creation": "2013-02-18 13:36:19", "docstatus": 0, - "modified": "2013-11-26 10:57:00", + "modified": "2013-12-17 14:32:51", "modified_by": "Administrator", "owner": "Administrator" }, @@ -321,28 +321,6 @@ "reqd": 0, "search_index": 0 }, - { - "doctype": "DocField", - "fieldname": "allow_print", - "fieldtype": "Check", - "hidden": 0, - "label": "Hide Print", - "oldfieldname": "allow_print", - "oldfieldtype": "Check", - "reqd": 0, - "search_index": 0 - }, - { - "doctype": "DocField", - "fieldname": "allow_email", - "fieldtype": "Check", - "hidden": 0, - "label": "Hide Email", - "oldfieldname": "allow_email", - "oldfieldtype": "Check", - "reqd": 0, - "search_index": 0 - }, { "doctype": "DocField", "fieldname": "allow_copy", diff --git a/core/page/data_import_tool/data_import_tool.py b/core/page/data_import_tool/data_import_tool.py index 7de21ede29..a79096081a 100644 --- a/core/page/data_import_tool/data_import_tool.py +++ b/core/page/data_import_tool/data_import_tool.py @@ -34,7 +34,8 @@ def get_doctype_options(): @webnotes.whitelist() def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data="No"): - webnotes.check_admin_or_system_manager() + import webnotes.permissions + webnotes.permissions.check_admin_or_system_manager() all_doctypes = all_doctypes=="Yes" if not parent_doctype: parent_doctype = doctype @@ -217,8 +218,9 @@ def get_template(doctype=None, parent_doctype=None, all_doctypes="No", with_data @webnotes.whitelist() def upload(rows = None, submit_after_import=None, ignore_encoding_errors=False, overwrite=False, ignore_links=False): """upload data""" + import webnotes.permissions webnotes.flags.mute_emails = True - webnotes.check_admin_or_system_manager() + webnotes.permissions.check_admin_or_system_manager() # extra input params params = json.loads(webnotes.form_dict.get("params") or '{}') diff --git a/core/page/permission_manager/permission_manager.js b/core/page/permission_manager/permission_manager.js index 4f8488bb38..5421f3ad6b 100644 --- a/core/page/permission_manager/permission_manager.js +++ b/core/page/permission_manager/permission_manager.js @@ -9,7 +9,7 @@ wn.pages['permission-manager'].onload = function(wrapper) { \

"+wn._("Quick Help for Setting Permissions")+":

\
    \ -
  1. "+wn._("Permissions are set on Roles and Document Types (called DocTypes) by restricting read, edit, make new, submit, cancel, amend and report rights.")+"
  2. \ +
  3. "+wn._("Permissions are set on Roles and Document Types (called DocTypes) by restricting read, edit, make new, submit, cancel, amend, report, import, export, print, email and restrict rights.")+"
  4. \
  5. "+wn._("Permissions translate to Users based on what Role they are assigned")+".
  6. \
  7. "+wn._("To set user roles, just go to Setup > Users and click on the user to assign roles.")+"
  8. \
  9. "+wn._("The system provides pre-defined roles, but you can add new roles to set finer permissions")+".
  10. \ @@ -45,8 +45,9 @@ wn.pages['permission-manager'].onload = function(wrapper) { \ \

    "+wn._("Advanced Settings")+":

    \ -

    "+wn._("To further restrict permissions based on certain values, like Company or Territory in a document, please go to User Restrictions")+"

    "+ - "

    "+wn._("Once you have set this, the users will only be able access documents where the link (e.g Company) exists.")+"


    \ +

    "+wn._("To further restrict permissions based on certain values, like Company or Territory in a document, please go to User Restrictions")+"

    "+ + "

    "+wn._("Once you have set this, the users will only be able access documents where the link (e.g Company) exists.")+"

    "+ + "

    "+wn._("Apart from System Manager, roles with Restrict permission can restrict other users for that Document Type")+"


    \

    "+wn._("If these instructions where not helpful, please add in your suggestions at GitHub Issues")+"

    \ \ "); @@ -160,37 +161,42 @@ wn.PermissionEngine = Class.extend({ }, show_permission_table: function(perm_list) { var me = this; - this.table = $("\ - \ - \ -
    ").appendTo(this.body); + this.table = $("
    \ + \ + \ + \ +
    \ +
    ").appendTo(this.body); - $.each([["Document Type", 150], ["Role", 100], ["Level",50], - ["Read", 50], ["Edit", 50], ["Make New", 50], - ["Submit", 50], ["Cancel", 50], ["Amend", 50], ["Report", 50], - ["Condition", 150], ["", 50]], function(i, col) { + $.each([["Document Type", 150], ["Role", 150], ["Level", 40], + ["Permissions", 270], ["Condition", 100], ["", 40]], function(i, col) { $("").html(col[0]).css("width", col[1]+"px") .appendTo(me.table.find("thead tr")); }); var add_cell = function(row, d, fieldname, is_check) { - var cell = $("").appendTo(row).attr("data-fieldname", fieldname); - if(is_check) { - if(d.permlevel > 0 && ["read", "write"].indexOf(fieldname)==-1) { - cell.html("-"); - } else { - var input = $("") - .prop("checked", d[fieldname] ? true: false) - .attr("data-ptype", fieldname) - .attr("data-name", d.name) - .attr("data-doctype", d.parent) - .appendTo(cell); - } - } else { - cell.html(d[fieldname]); + return $("").appendTo(row) + .attr("data-fieldname", fieldname) + .html(d[fieldname]); + }; + + var add_check = function(cell, d, fieldname) { + if(d.permlevel > 0 && ["read", "write"].indexOf(fieldname)==-1) { + return; } - return cell; - } + + var checkbox = $("
    \ + \ +
    ").appendTo(cell) + .attr("data-fieldname", fieldname) + .css("text-transform", "capitalize"); + + checkbox.find("input") + .prop("checked", d[fieldname] ? true: false) + .attr("data-ptype", fieldname) + .attr("data-name", d.name) + .attr("data-doctype", d.parent) + }; $.each(perm_list, function(i, d) { if(!d.permlevel) d.permlevel = 0; @@ -203,13 +209,21 @@ wn.PermissionEngine = Class.extend({ cell.css("font-weight", "bold"); row.addClass("warning"); } - add_cell(row, d, "read", true); - add_cell(row, d, "write", true); - add_cell(row, d, "create", true); - add_cell(row, d, "submit", true); - add_cell(row, d, "cancel", true); - add_cell(row, d, "amend", true); - add_cell(row, d, "report", true); + + var perm_cell = add_cell(row, d, "permissions").css("padding-top", 0); + var perm_container = $("
    ").appendTo(perm_cell); + add_check(perm_container, d, "read"); + add_check(perm_container, d, "write"); + add_check(perm_container, d, "create"); + add_check(perm_container, d, "submit"); + add_check(perm_container, d, "cancel"); + add_check(perm_container, d, "amend"); + add_check(perm_container, d, "report"); + add_check(perm_container, d, "import"); + add_check(perm_container, d, "export"); + add_check(perm_container, d, "print"); + add_check(perm_container, d, "email"); + add_check(perm_container, d, "restrict"); // buttons me.add_match_button(row, d); diff --git a/core/page/user_properties/user_properties.js b/core/page/user_properties/user_properties.js index 28f55410a7..b18ac96a8e 100644 --- a/core/page/user_properties/user_properties.js +++ b/core/page/user_properties/user_properties.js @@ -79,7 +79,7 @@ wn.UserProperties = Class.extend({ }); }, get_link_names: function() { - return $.map(this.options.link_fields, function(l) { return l[0]; }); + return this.options.link_fields; }, set_from_route: function() { var me = this; diff --git a/core/page/user_properties/user_properties.py b/core/page/user_properties/user_properties.py index d4357743b4..fdfe7b9f7e 100644 --- a/core/page/user_properties/user_properties.py +++ b/core/page/user_properties/user_properties.py @@ -4,30 +4,41 @@ from __future__ import unicode_literals import webnotes import webnotes.defaults +import webnotes.permissions @webnotes.whitelist() def get_users_and_links(): - webnotes.only_for(("Restriction Manager", "System Manager")) return { "users": webnotes.conn.sql_list("""select name from tabProfile where ifnull(enabled,0)=1 and name not in ("Administrator", "Guest")"""), - "link_fields": webnotes.conn.sql("""select name, name from tabDocType - where ifnull(issingle,0)=0 and ifnull(istable,0)=0""") + "link_fields": get_restrictable_doctypes() } @webnotes.whitelist() def get_properties(parent=None, defkey=None, defvalue=None): - webnotes.only_for(("Restriction Manager", "System Manager")) + if defkey and not webnotes.permissions.can_restrict(defkey, defvalue): + raise webnotes.PermissionError + conditions, values = _build_conditions(locals()) - return webnotes.conn.sql("""select name, parent, defkey, defvalue + properties = webnotes.conn.sql("""select name, parent, defkey, defvalue from tabDefaultValue where parent not in ('Control Panel', '__global') and substr(defkey,1,1)!='_' and parenttype='Restriction' {conditions} order by parent, defkey""".format(conditions=conditions), values, as_dict=True) + + if not defkey: + out = [] + doctypes = get_restrictable_doctypes() + for p in properties: + if p.defkey in doctypes: + out.append(p) + properties = out + + return properties def _build_conditions(filters): conditions = [] @@ -40,17 +51,34 @@ def _build_conditions(filters): return "\n".join(conditions), values @webnotes.whitelist() -def remove(user, name): - webnotes.only_for(("Restriction Manager", "System Manager")) +def remove(user, name, defkey, defvalue): + if not webnotes.permissions.can_restrict_user(user, defkey, defvalue): + raise webnotes.PermissionError + webnotes.defaults.clear_default(name=name) @webnotes.whitelist() def add(user, defkey, defvalue): - webnotes.only_for(("Restriction Manager", "System Manager")) + if not webnotes.permissions.can_restrict_user(user, defkey, defvalue): + raise webnotes.PermissionError # check if already exists d = webnotes.conn.sql("""select name from tabDefaultValue where parent=%s and parenttype='Restriction' and defkey=%s and defvalue=%s""", (user, defkey, defvalue)) if not d: - webnotes.defaults.add_default(defkey, defvalue, user, "Restriction") \ No newline at end of file + webnotes.defaults.add_default(defkey, defvalue, user, "Restriction") + +def get_restrictable_doctypes(): + user_roles = webnotes.get_roles() + condition = "" + values = [] + if "System Manager" not in user_roles: + condition = """and exists(select `tabDocPerm`.name from `tabDocPerm` + where `tabDocPerm`.parent=`tabDocType`.name and restrict=1 + and `tabDocPerm`.name in ({roles}))""".format(roles=", ".join(["%s"]*len(user_roles))) + values = user_roles + + return webnotes.conn.sql_list("""select name from tabDocType + where ifnull(issingle,0)=0 and ifnull(istable,0)=0 {condition}""".format(condition=condition), + values) diff --git a/public/js/wn/form/infobar.js b/public/js/wn/form/infobar.js index 87ce143a26..69cb9397c8 100644 --- a/public/js/wn/form/infobar.js +++ b/public/js/wn/form/infobar.js @@ -35,7 +35,7 @@ wn.ui.form.InfoBar = Class.extend({ function() { me.frm.toolbar.show_linked_with(); }); // link to user restrictions - if(wn.user.can_restrict()) { + if(wn.model.can_restrict(me.frm.doctype)) { this.$user_properties = this.appframe.add_icon_btn("2", "icon-shield", wn._("User Permission Restrictions"), function() { wn.route_options = { diff --git a/public/js/wn/misc/user.js b/public/js/wn/misc/user.js index 0b22d6d163..57a69b8c44 100644 --- a/public/js/wn/misc/user.js +++ b/public/js/wn/misc/user.js @@ -125,10 +125,6 @@ $.extend(wn.user, { is_report_manager: function() { return wn.user.has_role(['Administrator', 'System Manager', 'Report Manager']); }, - - can_restrict: function() { - return wn.user.has_role(["Restriction Manager", "System Manager"]); - }, }); wn.session_alive = true; diff --git a/public/js/wn/model/model.js b/public/js/wn/model/model.js index cccfb43ce0..bc340125b3 100644 --- a/public/js/wn/model/model.js +++ b/public/js/wn/model/model.js @@ -120,24 +120,24 @@ $.extend(wn.model, { }, can_create: function(doctype) { - return wn.boot.profile.can_create.indexOf(doctype)!=-1; + return wn.boot.profile.can_create.indexOf(doctype)!==-1; }, can_read: function(doctype) { - return wn.boot.profile.can_read.indexOf(doctype)!=-1; + return wn.boot.profile.can_read.indexOf(doctype)!==-1; }, can_write: function(doctype) { - return wn.boot.profile.can_write.indexOf(doctype)!=-1; + return wn.boot.profile.can_write.indexOf(doctype)!==-1; }, can_get_report: function(doctype) { - return wn.boot.profile.can_get_report.indexOf(doctype)!=-1; + return wn.boot.profile.can_get_report.indexOf(doctype)!==-1; }, can_delete: function(doctype) { if(!doctype) return false; - return wn.boot.profile.can_cancel.indexOf(doctype)!=-1; + return wn.boot.profile.can_cancel.indexOf(doctype)!==-1; }, is_submittable: function(doctype) { @@ -145,6 +145,26 @@ $.extend(wn.model, { return locals.DocType[doctype] && locals.DocType[doctype].is_submittable; }, + can_import: function(doctype) { + return wn.boot.profile.can_import.indexOf(doctype)!==-1; + }, + + can_export: function(doctype) { + return wn.boot.profile.can_export.indexOf(doctype)!==-1; + }, + + can_print: function(doctype) { + return wn.boot.profile.can_print.indexOf(doctype)!==-1; + }, + + can_email: function(doctype) { + return wn.boot.profile.can_email.indexOf(doctype)!==-1; + }, + + can_restrict: function(doctype) { + return wn.boot.profile.can_restrict.indexOf(doctype)!==-1; + }, + has_value: function(dt, dn, fn) { // return true if property has value var val = locals[dt] && locals[dt][dn] && locals[dt][dn][fn]; diff --git a/public/js/wn/views/query_report.js b/public/js/wn/views/query_report.js index 0134896325..4369cfdebf 100644 --- a/public/js/wn/views/query_report.js +++ b/public/js/wn/views/query_report.js @@ -74,7 +74,7 @@ wn.views.QueryReport = Class.extend({ "icon-download"); wn.utils.disable_export_btn(export_btn); - if(wn.user.can_restrict()) { + if(wn.model.can_restrict("Report")) { this.appframe.add_primary_action(wn._("User Restrictions"), function() { wn.route_options = { property: "Report", diff --git a/public/js/wn/views/reportview.js b/public/js/wn/views/reportview.js index adffc5e438..864e830489 100644 --- a/public/js/wn/views/reportview.js +++ b/public/js/wn/views/reportview.js @@ -514,7 +514,7 @@ wn.views.ReportView = wn.ui.Listing.extend({ make_user_restrictions: function() { var me = this; - if(this.docname && wn.user.can_restrict()) { + if(this.docname && wn.model.can_restrict("Report")) { this.page.appframe.add_button(wn._("User Permission Restrictions"), function() { wn.route_options = { property: "Report", diff --git a/webnotes/__init__.py b/webnotes/__init__.py index e8ff011192..6ac257e762 100644 --- a/webnotes/__init__.py +++ b/webnotes/__init__.py @@ -327,86 +327,18 @@ def get_roles(username=None): return user.get_roles() else: return webnotes.profile.Profile(username).get_roles() - -def check_admin_or_system_manager(): - if ("System Manager" not in get_roles()) and \ - (session.user!="Administrator"): - msgprint("Only Allowed for Role System Manager or Administrator", raise_exception=True) def has_permission(doctype, ptype="read", refdoc=None): - """check if user has permission""" - if session.user=="Administrator" or conn.get_value("DocType", doctype, "istable")==1: - return True + import webnotes.permissions + return webnotes.permissions.has_permission(doctype, ptype, refdoc) - meta = get_doctype(doctype) - - # get user permissions - perms = get_user_perms(meta, ptype) - - if not perms: - return False - elif refdoc: - if isinstance(refdoc, basestring): - refdoc = doc(meta[0].name, refdoc) - - if has_only_permitted_data(meta, refdoc) and has_match(perms, refdoc): - return True - else: - return False - else: - return True - -def get_user_perms(meta, ptype, user=None): - from webnotes.utils import cint - user_roles = get_roles(user) - - return [p for p in meta.get({"doctype": "DocPerm"}) - if cint(p.get(ptype))==1 and cint(p.permlevel)==0 and (p.role=="All" or p.role in user_roles)] +def clear_perms(doctype): + conn.sql("""delete from tabDocPerm where parent=%s""", doctype) -def has_only_permitted_data(meta, refdoc): - from webnotes.defaults import get_restrictions - - has_restricted_data = False - restrictions = get_restrictions() - - if restrictions: - fields_to_check = meta.get_restricted_fields(restrictions.keys()) - - if meta[0].name in restrictions: - fields_to_check.append(_dict({"label":"Name", "fieldname":"name", "options": meta[0].name})) - - for df in fields_to_check: - if refdoc.get(df.fieldname) and refdoc.get(df.fieldname) not in restrictions[df.options]: - msg = "{not_allowed}: {doctype} {having} {label} = {value}".format( - not_allowed=_("Sorry, you are not allowed to access"), doctype=_(df.options), - having=_("having"), label=_(df.label), value=refdoc.get(df.fieldname)) - - if refdoc.parentfield: - msg = "{doctype}, {row} #{idx}, ".format(doctype=_(refdoc.doctype), - row=_("Row"), idx=refdoc.idx) + msg - - msgprint(msg) - has_restricted_data = True - - if has_restricted_data: - # check all restrictions before returning - return False - else: - return True - -def has_match(perms, refdoc): - """check owner match (if exists)""" - for p in perms: - if p.get("match")=="owner": - if refdoc.get("owner")==local.session.user: - # owner matches :) - return True - else: - # found a permission without owner match :) - return True - - # no match :( - return False +def reset_perms(doctype): + clear_perms(doctype) + reload_doc(conn.get_value("DocType", doctype, "module"), + "DocType", doctype, force=True) def generate_hash(): """Generates random hash for session id""" @@ -472,13 +404,6 @@ def delete_doc(doctype=None, name=None, doclist = None, force=0, ignore_doctypes webnotes.model.delete_doc.delete_doc(doctype, name, doclist, force, ignore_doctypes, for_reload, ignore_permissions) -def clear_perms(doctype): - conn.sql("""delete from tabDocPerm where parent=%s""", doctype) - -def reset_perms(doctype): - clear_perms(doctype) - reload_doc(conn.get_value("DocType", doctype, "module"), "DocType", doctype, force=True) - def reload_doc(module, dt=None, dn=None, plugin=None, force=False): import webnotes.modules return webnotes.modules.reload_doc(module, dt, dn, plugin=plugin, force=force) diff --git a/webnotes/install_lib/install.py b/webnotes/install_lib/install.py index df2a630424..95ef1199d9 100755 --- a/webnotes/install_lib/install.py +++ b/webnotes/install_lib/install.py @@ -150,7 +150,6 @@ class Installer: 'parenttype':'Profile', 'parentfield':'user_roles'}, {'doctype': "Role", "role_name": "Report Manager"}, - {'doctype': "Role", "role_name": "Restriction Manager"}, ] webnotes.conn.begin() diff --git a/webnotes/model/bean.py b/webnotes/model/bean.py index 6a0e2f871e..60ded8aa76 100644 --- a/webnotes/model/bean.py +++ b/webnotes/model/bean.py @@ -13,6 +13,7 @@ import webnotes from webnotes import _, msgprint from webnotes.utils import cint, cstr, flt from webnotes.model.doc import Document +import webnotes.permissions try: from startup.bean_handlers import on_method except ImportError: @@ -459,7 +460,7 @@ class Bean: has_restricted_data = False for d in self.doclist: - if not webnotes.has_only_permitted_data(webnotes.get_doctype(d.doctype), d): + if not webnotes.permissions.has_only_permitted_data(webnotes.get_doctype(d.doctype), d): has_restricted_data = True if has_restricted_data: diff --git a/webnotes/permissions.py b/webnotes/permissions.py new file mode 100644 index 0000000000..97589e9d2f --- /dev/null +++ b/webnotes/permissions.py @@ -0,0 +1,129 @@ +# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import webnotes +from webnotes import _, msgprint, _dict +from webnotes.utils import cint + +def check_admin_or_system_manager(): + if ("System Manager" not in webnotes.get_roles()) and \ + (webnotes.session.user!="Administrator"): + msgprint("Only Allowed for Role System Manager or Administrator", raise_exception=True) + +def has_permission(doctype, ptype="read", refdoc=None): + """check if user has permission""" + if webnotes.session.user=="Administrator" or webnotes.conn.get_value("DocType", doctype, "istable")==1: + return True + + meta = webnotes.get_doctype(doctype) + + # get user permissions + perms = get_user_perms(meta, ptype) + + if not perms: + return False + elif refdoc: + if isinstance(refdoc, basestring): + refdoc = webnotes.doc(meta[0].name, refdoc) + + if has_only_permitted_data(meta, refdoc) and has_match(perms, refdoc): + return True + else: + return False + else: + return True + +def get_user_perms(meta, ptype, user=None): + from webnotes.utils import cint + user_roles = webnotes.get_roles(user) + + return [p for p in meta.get({"doctype": "DocPerm"}) + if cint(p.get(ptype))==1 and cint(p.permlevel)==0 and (p.role=="All" or p.role in user_roles)] + +def has_only_permitted_data(meta, refdoc): + from webnotes.defaults import get_restrictions + + has_restricted_data = False + restrictions = get_restrictions() + + if restrictions: + fields_to_check = meta.get_restricted_fields(restrictions.keys()) + + if meta[0].name in restrictions: + fields_to_check.append(_dict({"label":"Name", "fieldname":"name", "options": meta[0].name})) + + for df in fields_to_check: + if refdoc.get(df.fieldname) and refdoc.get(df.fieldname) not in restrictions[df.options]: + msg = "{not_allowed}: {doctype} {having} {label} = {value}".format( + not_allowed=_("Sorry, you are not allowed to access"), doctype=_(df.options), + having=_("having"), label=_(df.label), value=refdoc.get(df.fieldname)) + + if refdoc.parentfield: + msg = "{doctype}, {row} #{idx}, ".format(doctype=_(refdoc.doctype), + row=_("Row"), idx=refdoc.idx) + msg + + msgprint(msg) + has_restricted_data = True + + if has_restricted_data: + # check all restrictions before returning + return False + else: + return True + +def has_match(perms, refdoc): + """check owner match (if exists)""" + for p in perms: + if p.get("match")=="owner": + if refdoc.get("owner")==webnotes.local.session.user: + # owner matches :) + return True + else: + # found a permission without owner match :) + return True + + # no match :( + return False + +def can_restrict_user(user, doctype, docname=None): + if not can_restrict(doctype, docname): + return False + + meta = webnotes.get_doctype(doctype) + + # check if target user does not have restrict permission + if has_non_restrict_role(meta, user): + return True + + return False + +def can_restrict(doctype, docname=None): + # System Manager can always restrict + if "System Manager" in webnotes.get_roles(): + return True + + meta = webnotes.get_doctype(doctype) + + # check if current user has read permission for docname + if docname and not has_permission(doctype, "read", docname): + return False + + # check if current user has a role with restrict permission + if not has_restrict_permission(meta): + return False + + return True + +def has_restrict_permission(meta=None, user=None): + return any((perm for perm in get_user_perms(meta, "read", user) + if cint(perm.restrict)==1)) + +def has_non_restrict_role(meta, user): + # check if target user does not have restrict permission + if has_restrict_permission(meta, user): + return False + + # and has non-restrict role + return any((perm for perm in get_user_perms(meta, "read", user) + if cint(perm.restrict)==0)) \ No newline at end of file diff --git a/webnotes/profile.py b/webnotes/profile.py index da4ae5d984..b70a214d39 100644 --- a/webnotes/profile.py +++ b/webnotes/profile.py @@ -22,6 +22,11 @@ class Profile: self.can_cancel = [] self.can_search = [] self.can_get_report = [] + self.can_import = [] + self.can_export = [] + self.can_print = [] + self.can_email = [] + self.can_restrict = [] self.allow_modules = [] self.in_create = [] @@ -43,7 +48,8 @@ class Profile: """build map of permissions at level 0""" self.perm_map = {} - for r in webnotes.conn.sql("""select parent, `read`, `write`, `create`, `submit`, `cancel`, `report` + for r in webnotes.conn.sql("""select parent, `read`, `write`, `create`, `submit`, `cancel`, + `report`, `import`, `export`, `print`, `email`, `restrict` from tabDocPerm where docstatus=0 and ifnull(permlevel,0)=0 and parent not like "old_parent:%%" @@ -54,7 +60,8 @@ class Profile: if not dt in self.perm_map: self.perm_map[dt] = {} - for k in ('read', 'write', 'create', 'submit', 'cancel', 'report'): + for k in ('read', 'write', 'create', 'submit', 'cancel', 'report' + 'import', 'export', 'print', 'email', 'restrict'): if not self.perm_map[dt].get(k): self.perm_map[dt][k] = r.get(k) @@ -91,6 +98,10 @@ class Profile: if (p.get('read') or p.get('write') or p.get('create')): if p.get('report'): self.can_get_report.append(dt) + for key in ("import", "export", "print", "email", "restrict"): + if p.get(key): + getattr(self, "can_" + key).append(dt) + if not dtp.get('istable'): if not dtp.get('issingle') and not dtp.get('read_only'): self.can_search.append(dt) @@ -144,15 +155,13 @@ class Profile: d['roles'] = self.get_roles() d['defaults'] = self.get_defaults() - d['can_create'] = self.can_create - d['can_write'] = self.can_write - d['can_read'] = list(set(self.can_read)) - d['can_cancel'] = list(set(self.can_cancel)) - d['can_get_report'] = list(set(self.can_get_report)) - d['allow_modules'] = self.allow_modules - d['all_read'] = self.all_read - d['can_search'] = list(set(self.can_search)) - d['in_create'] = self.in_create + + for key in ("can_create", "can_write", "can_read", "can_cancel", + "can_get_report", "allow_modules", "all_read", "can_search", + "in_create", "can_export", "can_import", "can_print", "can_email", + "can_restrict"): + + d[key] = list(set(getattr(self, key))) return d diff --git a/webnotes/widgets/reportview.py b/webnotes/widgets/reportview.py index 3db1fde6ad..37f25dd15a 100644 --- a/webnotes/widgets/reportview.py +++ b/webnotes/widgets/reportview.py @@ -187,6 +187,7 @@ def build_filter_conditions(filters, conditions): def build_match_conditions(doctype, fields=None, as_condition=True): """add match conditions if applicable""" + import webnotes.permissions match_filters = {} match_conditions = [] @@ -215,7 +216,7 @@ def build_match_conditions(doctype, fields=None, as_condition=True): # add owner match owner_match = True - for p in webnotes.get_user_perms(webnotes.local.reportview_doctypes[doctype], "read"): + for p in webnotes.permissions.get_user_perms(webnotes.local.reportview_doctypes[doctype], "read"): if not (p.match and p.match=="owner"): owner_match = False break diff --git a/website/doctype/blog_post/test_blog_post.py b/website/doctype/blog_post/test_blog_post.py index 53a0e6d3aa..fb258819d9 100644 --- a/website/doctype/blog_post/test_blog_post.py +++ b/website/doctype/blog_post/test_blog_post.py @@ -38,7 +38,10 @@ class TestBlogPost(unittest.TestCase): webnotes.clear_cache(doctype="Blog Post") profile = webnotes.bean("Profile", "test1@example.com") - profile.get_controller().add_roles(["Website Manager"]) + profile.get_controller().add_roles("Website Manager") + + profile = webnotes.bean("Profile", "test2@example.com") + profile.get_controller().add_roles("Blogger") webnotes.set_user("test1@example.com") @@ -86,7 +89,6 @@ class TestBlogPost(unittest.TestCase): post1 = webnotes.bean("Blog Post", "_test-blog-post-1") self.assertFalse(post1.has_read_perm()) - def test_owner_match_report(self): webnotes.conn.sql("""update tabDocPerm set `match`='owner' where parent='Blog Post' and ifnull(permlevel,0)=0""") @@ -95,7 +97,34 @@ class TestBlogPost(unittest.TestCase): names = [d.name for d in webnotes.get_list("Blog Post", fields=["name", "owner"])] self.assertTrue("_test-blog-post" in names) self.assertFalse("_test-blog-post-1" in names) - - + def test_restrict(self): + from core.page.user_properties.user_properties import add, remove, get_properties + + # restrictor can add restriction + webnotes.set_user("test1@example.com") + add("test2@example.com", "Blog Post", "_test-blog-post") + defname = get_properties("test2@example.com", "Blog Post", "_test-blog-post")[0].name + + webnotes.set_user("test2@example.com") + + # this user can't add restriction + self.assertRaises(webnotes.PermissionError, add, + "test2@example.com", "Blog Post", "_test-blog-post") + + # user can only access restricted blog post + bean = webnotes.bean("Blog Post", "_test-blog-post") + self.assertTrue(bean.has_read_perm()) + + # and not this one + bean = webnotes.bean("Blog Post", "_test-blog-post-1") + self.assertFalse(bean.has_read_perm()) + + # user cannot remove their own restriction + self.assertRaises(webnotes.PermissionError, remove, + "test2@example.com", defname, "Blog Post", "_test-blog-post") + + # but restrictor can + webnotes.set_user("test1@example.com") + remove("test2@example.com", defname, "Blog Post", "_test-blog-post") \ No newline at end of file