diff --git a/core/doctype/defaultvalue/defaultvalue.py b/core/doctype/defaultvalue/defaultvalue.py index b17817bed9..3701a2ecb9 100644 --- a/core/doctype/defaultvalue/defaultvalue.py +++ b/core/doctype/defaultvalue/defaultvalue.py @@ -13,4 +13,10 @@ def on_doctype_update(): where Key_name="defaultvalue_parent_defkey_index" """): webnotes.conn.commit() webnotes.conn.sql("""alter table `tabDefaultValue` - add index defaultvalue_parent_defkey_index(parent, defkey)""") \ No newline at end of file + add index defaultvalue_parent_defkey_index(parent, defkey)""") + + if not webnotes.conn.sql("""show index from `tabDefaultValue` + where Key_name="defaultvalue_parent_parenttype_index" """): + webnotes.conn.commit() + webnotes.conn.sql("""alter table `tabDefaultValue` + add index defaultvalue_parent_parenttype_index(parent, parentttype)""") \ No newline at end of file diff --git a/core/doctype/docfield/docfield.txt b/core/doctype/docfield/docfield.txt index 7362a518d7..3b0a612653 100644 --- a/core/doctype/docfield/docfield.txt +++ b/core/doctype/docfield/docfield.txt @@ -2,7 +2,7 @@ { "creation": "2013-02-22 01:27:33", "docstatus": 0, - "modified": "2013-07-10 14:54:08", + "modified": "2013-11-26 16:34:15", "modified_by": "Administrator", "owner": "Administrator" }, @@ -132,6 +132,13 @@ "print_width": "50px", "width": "50px" }, + { + "description": "User restrictions should not apply for this Link", + "doctype": "DocField", + "fieldname": "ignore_restriction", + "fieldtype": "Check", + "label": "Ignore Restriction" + }, { "doctype": "DocField", "fieldname": "width", diff --git a/core/page/permission_manager/permission_manager.js b/core/page/permission_manager/permission_manager.js index c519680ead..4f8488bb38 100644 --- a/core/page/permission_manager/permission_manager.js +++ b/core/page/permission_manager/permission_manager.js @@ -41,27 +41,12 @@ wn.pages['permission-manager'].onload = function(wrapper) {

"+wn._("Restricting By User")+":

\
    \
  1. "+wn._("To restrict a User of a particular Role to documents that are only self-created.")+ - wn._("Click on button in the 'Condition' column and select the option 'User is the creator of the document'")+".
  2. \ -
  3. "+wn._("To restrict a User of a particular Role to documents that are explicitly assigned to them")+ ":"+ - + wn._("create a Custom Field of type Link (Profile) and then use the 'Condition' settings to map that field to the Permission rule.")+ - "
\ + wn._("Click on button in the 'Condition' column and select the option 'User is the creator of the document'")+".\ \ \

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

\ -

"+wn._("To further restrict permissions based on certain values in a document, use the 'Condition' settings.")+"

"+ - wn._("For example: You want to restrict users to transactions marked with a certain property called 'Territory'")+":

\ -
    \ -
  1. "+wn._("Make sure that the transactions you want to restrict have a Link field 'territory' that maps to a 'Territory' master.")+" " - +wn._("If not, create a")+ - ""+wn._("Custom Field")+""+ wn._("of type Link")+".
  2. \ -
  3. "+wn._("In the Permission Manager, click on the button in the 'Condition' column for the Role you want to restrict.")+"
  4. \ -
  5. "+wn._("A new popup will open that will ask you to select further conditions.")+ - wn._("If the 'territory' Link Field exists, it will give you an option to select it")+".
  6. \ -
  7. "+wn._("Go to Setup > User Properties to set \ - 'territory' for diffent Users.")+"
  8. \ -
\ -

"+wn._("Once you have set this, the users will only be able access documents with that property.")+"

\ -
\ +

"+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._("If these instructions where not helpful, please add in your suggestions at GitHub Issues")+"

\ \ "); @@ -390,23 +375,6 @@ wn.PermissionEngine = Class.extend({ The user is the creator of the document.\ ").css("padding", "15px"); - // profile fields - $.each(me.get_profile_fields(perm.parent), function(i, d) { - $("").appendTo(dialog.body); - }); - - // add options for all link fields - $.each(me.get_link_fields(perm.parent), function(i, d) { - $("").appendTo(dialog.body); - }); - // button $("") .appendTo($("

").appendTo(dialog.body)) diff --git a/core/page/user_properties/user_properties.js b/core/page/user_properties/user_properties.js index 08d94a8626..30946e5220 100644 --- a/core/page/user_properties/user_properties.js +++ b/core/page/user_properties/user_properties.js @@ -1,19 +1,19 @@ wn.pages['user-properties'].onload = function(wrapper) { wn.ui.make_app_page({ parent: wrapper, - title: 'User Properties', + title: 'User Permission Restrictions', single_column: true }); - $(wrapper).find(".layout-main").html("

\ + $(wrapper).find(".layout-main").html("
\ \ \
\ -

"+wn._("Quick Help for User Properties")+":

\ +

"+wn._("Quick Help for Permission Restrictions")+":

\
    \ -
  1. "+wn._("You can set various 'properties' to Users to set default values and apply permission rules based on the value of these properties in various forms.")+"
  2. \ -
  3. "+wn._("These properties are Link Type fields from all Documents.")+"
  4. \ -
  5. "+wn._("These properties will appear as values in forms that contain them.")+"
  6. \ -
  7. "+wn._("These properties can also be used to 'assign' a particular document, whose property matches with the User's property to a User. These can be set using the Permission Manager")+"
  8. \ -
  9. "+wn._("A user can have multiple values for a property.")+"
  10. \ +
  11. "+wn._("Apart from the existing Permission Rules, you can apply addition restriction based on Type.")+"
  12. \ +
  13. "+wn._("These restrictions will apply for all transactions linked to the restricted record.") + +wn._("For example, if user X is restricted to company C, user X will not be able to see any transaction that has company C as a linked value.")+"
  14. \ +
  15. "+wn._("These will also be set as default values for those links.")+"
  16. \ +
  17. "+wn._("A user can be restricted to multiple records of the same type.")+"
  18. \
\
"); @@ -42,14 +42,12 @@ wn.UserProperties = Class.extend({ me.user_select = me.wrapper.appframe.add_select("users", ["Select User..."].concat(r.message.users)) - .css("width", "200px") .change(function() { me.set_route(); }); me.property_select = me.wrapper.appframe.add_select("links", ["Select Property..."].concat(me.get_link_names())) - .css("width", "200px") .change(function() { me.set_route(); }); @@ -121,7 +119,7 @@ wn.UserProperties = Class.extend({ \ ").appendTo(this.body); - $.each([[wn._("User"), 150], [wn._("Property"), 150], [wn._("Value"),150], ["", 50]], + $.each([[wn._("User"), 150], [wn._("Type"), 150], [wn._("Restricted To"),150], ["", 50]], function(i, col) { $("").html(col[0]).css("width", col[1]+"px") .appendTo(me.table.find("thead tr")); @@ -168,7 +166,7 @@ wn.UserProperties = Class.extend({ show_add_property: function() { var me = this; - $("") + $("") .appendTo($("

").appendTo(this.body)) .click(function() { var d = new wn.ui.Dialog({ @@ -229,6 +227,5 @@ wn.UserProperties = Class.extend({ }); d.show(); }); - } }) diff --git a/core/page/user_properties/user_properties.py b/core/page/user_properties/user_properties.py index 50a80384da..0d0c268b06 100644 --- a/core/page/user_properties/user_properties.py +++ b/core/page/user_properties/user_properties.py @@ -8,28 +8,12 @@ import webnotes.defaults @webnotes.whitelist() def get_users_and_links(): webnotes.only_for(("System Manager", "Administrator")) - links, all_fields = [], [] - - for l in webnotes.conn.sql("""select tabDocField.fieldname, tabDocField.options - from tabDocField, tabDocType - where tabDocField.fieldtype='Link' - and tabDocField.parent = tabDocType.name - and ifnull(tabDocType.istable,0)=0 - and ifnull(tabDocType.issingle,0)=0 - and tabDocField.parent not in ('[Select]', 'DocType', 'Module Def') - """) + webnotes.conn.sql("""select fieldname, options - from `tabCustom Field` where fieldtype='Link'"""): - if not l[0] in all_fields: - links.append([l[0], l[1]]) - all_fields.append(l[0]) - - links.sort() - return { "users": [d[0] for d in webnotes.conn.sql("""select name from tabProfile where ifnull(enabled,0)=1 and name not in ("Administrator", "Guest")""")], - "link_fields": links + "link_fields": webnotes.conn.sql("""select name, name from tabDocType + where ifnull(issingle,0)=0 and ifnull(istable,0)=0""") } @webnotes.whitelist() @@ -38,6 +22,7 @@ def get_properties(user=None, key=None): return webnotes.conn.sql("""select name, parent, defkey, defvalue from tabDefaultValue where parent!='Control Panel' + and parenttype='Restriction' and substr(defkey,1,1)!='_' %s%s order by parent, defkey""" % (\ user and (" and parent='%s'" % user) or "", @@ -51,8 +36,4 @@ def remove(user, name): @webnotes.whitelist() def add(parent, defkey, defvalue): webnotes.only_for(("System Manager", "Administrator")) - webnotes.defaults.add_user_default(defkey, defvalue, parent) - -def get_defvalue(doctype, txt, searchfield, start, page_len, filters): - return webnotes.conn.sql("""select name from `tab%s` where name like %s limit 20""" % - (filters.get("doctype"), "%s"), "%s%%" % (txt,)) \ No newline at end of file + webnotes.add_default(defkey, defvalue, parent, "Restriction") \ No newline at end of file diff --git a/public/js/wn/form/control.js b/public/js/wn/form/control.js index 462062d922..26e7abdc17 100644 --- a/public/js/wn/form/control.js +++ b/public/js/wn/form/control.js @@ -104,18 +104,19 @@ wn.ui.form.ControlImage = wn.ui.form.Control.extend({ this._super(); var me = this; this.$wrapper - .css({"margin-bottom": "10px", "margin-right": "15px", "float": "right", "text-align": "right", "max-width": "100%"}) - .on("refresh", function() { - me.$wrapper.empty(); - if(me.df.options && me.frm.doc[me.df.options]) { - $("") - .appendTo(me.$wrapper); - } else { - $("

") - .appendTo(me.$wrapper) - } - return false; - }) + .css({"margin-bottom": "10px", "margin-right": "15px", "float": "right", + "text-align": "right", "max-width": "100%"}) + .on("refresh", function() { + me.$wrapper.empty(); + if(me.df.options && me.frm.doc[me.df.options]) { + $("") + .appendTo(me.$wrapper); + } else { + $("
") + .appendTo(me.$wrapper) + } + return false; + }); } }); diff --git a/public/js/wn/model/create_new.js b/public/js/wn/model/create_new.js index 86f31c2c7e..3808174e9e 100644 --- a/public/js/wn/model/create_new.js +++ b/public/js/wn/model/create_new.js @@ -64,7 +64,12 @@ $.extend(wn.model, { "Today": dateutil.get_today(), } - if(wn.defaults.get_user_default(df.fieldname)) + if(df.fieldtype==="Link" && wn.boot.restrictions + && df.ignore_restrictions != 1 + && wn.boot.restrictions[df.options] + && (wn.boot.restrictions[df.options].length===1)) + return wn.boot.restrictions[df.options][0]; + else if(wn.defaults.get_user_default(df.fieldname)) return wn.defaults.get_user_default(df.fieldname); else if(df["default"] && df["default"][0]===":") return wn.model.get_default_from_boot_docs(df, doc, parent_doc); diff --git a/webnotes/__init__.py b/webnotes/__init__.py index 677e9be363..eec36d3186 100644 --- a/webnotes/__init__.py +++ b/webnotes/__init__.py @@ -71,6 +71,7 @@ _response = local("_response") session = local("session") user = local("user") flags = local("flags") +restrictions = local("restrictions") error_log = local("error_log") debug_log = local("debug_log") @@ -93,6 +94,7 @@ def init(site=None): local.initialised = True local.flags = _dict({}) local.rollback_observers = [] + local.restrictions = None def destroy(): """closes connection and releases werkzeug local""" @@ -347,36 +349,28 @@ def has_permission(doctype, ptype="read", refdoc=None): return perms and True or False def has_match(meta, perms, refdoc): - from webnotes.defaults import get_user_default_as_list + from webnotes.defaults import get_restrictions + + restrictions = get_restrictions() + if not restrictions: + return True if isinstance(refdoc, basestring): refdoc = doc(meta[0].name, refdoc) - match_failed = {} - for p in perms: - if p.match: - if ":" in p.match: - keys = p.match.split(":") - else: - keys = [p.match, p.match] - - if refdoc.fields.get(keys[0],"[No Value]") in get_user_default_as_list(keys[1]): - return True - else: - match_failed[keys[0]] = refdoc.fields.get(keys[0],"[No Value]") - else: - # found a permission without a match - return True - - # no valid permission found - if match_failed: - msg = _("Not allowed for: ") - for key in match_failed: - msg += "\n" + (meta.get_field(key) and meta.get_label(key) or key) \ - + " = " + (match_failed[key] or "None") - msgprint(msg) + fields_to_check = meta.get({"DocType":"DocField", "parent":meta[0].name, "fieldtype":"Link", + "options":("in", restrictions.keys()), "ignore_restrictions":("!=", 1)}) - return False + if meta[0].name in restrictions: + fields_to_check.append(_dict({"label":"Name", "fieldname":"name"})) + + for df in fields_to_check: + if refdoc.get(df.fieldname) not in restrictions[meta[0].name]: + msg = _("Not allowed for: ") + df.label + " equals " + refdoc.get(df.fieldname) + msgprint(msg) + return False + + return True def generate_hash(): """Generates random hash for session id""" diff --git a/webnotes/boot.py b/webnotes/boot.py index 2bb53276f9..826b68bc3d 100644 --- a/webnotes/boot.py +++ b/webnotes/boot.py @@ -27,6 +27,7 @@ def get_bootinfo(): # system info bootinfo['control_panel'] = webnotes._dict(cp.copy()) bootinfo['sysdefaults'] = webnotes.defaults.get_defaults() + bootinfo['restrictions'] = webnotes.defaults.get_restrictions() bootinfo['server_date'] = webnotes.utils.nowdate() bootinfo["send_print_in_body_and_attachment"] = webnotes.conn.get_value("Email Settings", None, "send_print_in_body_and_attachment") diff --git a/webnotes/db.py b/webnotes/db.py index a612260018..013a895209 100644 --- a/webnotes/db.py +++ b/webnotes/db.py @@ -414,14 +414,14 @@ class Database: def get_global(self, key, user='__global'): return self.get_default(key, user) - def set_default(self, key, val, parent="Control Panel"): + def set_default(self, key, val, parent="Control Panel", parenttype=None): """set control panel default (tabDefaultVal)""" import webnotes.defaults - webnotes.defaults.set_default(key, val, parent) + webnotes.defaults.set_default(key, val, parent, parenttype) - def add_default(self, key, val, parent="Control Panel"): + def add_default(self, key, val, parent="Control Panel", parenttype=None): import webnotes.defaults - webnotes.defaults.add_default(key, val, parent) + webnotes.defaults.add_default(key, val, parent, parenttype) def get_default(self, key, parent="Control Panel"): """get default value""" diff --git a/webnotes/defaults.py b/webnotes/defaults.py index c9ccb1b815..6f50a58c9f 100644 --- a/webnotes/defaults.py +++ b/webnotes/defaults.py @@ -7,11 +7,11 @@ import memc # User -def set_user_default(key, value, user=None): - set_default(key, value, user or webnotes.session.user) +def set_user_default(key, value, user=None, parenttype=None): + set_default(key, value, user or webnotes.session.user, parenttype) -def add_user_default(key, value, user=None): - add_default(key, value, user or webnotes.session.user) +def add_user_default(key, value, user=None, parenttype=None): + add_default(key, value, user or webnotes.session.user, parenttype) def get_user_default(key, user=None): d = get_defaults(user or webnotes.session.user).get(key, None) @@ -20,7 +20,18 @@ def get_user_default(key, user=None): def get_user_default_as_list(key, user=None): d = get_defaults(user or webnotes.session.user).get(key, None) return (not isinstance(d, list)) and [d] or d - + +def get_restrictions(): + if webnotes.local.restrictions is None: + out = {} + for key, value in webnotes.conn.sql("""select defkey, defvalue + from tabDefaultValue where parent=%s and parenttype='Restriction'""", webnotes.session.user): + out.setdefault(key, []) + out[key].append(value) + + webnotes.local.restrictions = out + return webnotes.local.restrictions + def get_defaults(user=None): if not user and webnotes.session: user = webnotes.session.user @@ -57,21 +68,21 @@ def get_global_default(key): # Common -def set_default(key, value, parent): +def set_default(key, value, parent, parenttype="Control Panel"): if webnotes.conn.sql("""select defkey from `tabDefaultValue` where defkey=%s and parent=%s """, (key, parent)): # update - webnotes.conn.sql("""update `tabDefaultValue` set defvalue=%s - where parent=%s and defkey=%s""", (value, parent, key)) + webnotes.conn.sql("""update `tabDefaultValue` set defvalue=%s, parenttype=%s + where parent=%s and defkey=%s""", (value, parenttype, parent, key)) clear_cache(parent) else: add_default(key, value, parent) -def add_default(key, value, parent): +def add_default(key, value, parent, parenttype=None): d = webnotes.doc({ "doctype": "DefaultValue", "parent": parent, - "parenttype": "Control Panel", + "parenttype": parenttype or "Control Panel", "parentfield": "system_defaults", "defkey": key, "defvalue": value @@ -124,28 +135,10 @@ def get_defaults_for(parent="Control Panel"): elif d.defvalue is not None: defaults[d.defkey] = d.defvalue - if webnotes.session and parent == webnotes.session.user: - defaults.update(get_defaults_for_match(defaults)) - webnotes.cache().set_value("__defaults:" + parent, defaults) return defaults -def get_defaults_for_match(userd): - """ if a profile based match condition exists for a user's role - and no user property is specified for that match key, - set default value as user's profile for that match key""" - user_roles = webnotes.get_roles() - out = {} - - for role, match in webnotes.conn.sql("""select distinct role, `match` - from `tabDocPerm` where ifnull(permlevel, 0)=0 and `read`=1 - and `match` like "%:user" """): - if role in user_roles and match.split(":")[0] not in userd: - out[match.split(":")[0]] = webnotes.session.user - - return out - def clear_cache(parent=None): def all_profiles(): return webnotes.conn.sql_list("select name from tabProfile") + ["Control Panel", "__global"] diff --git a/webnotes/model/create_new.py b/webnotes/model/create_new.py index 1fac653174..4a81a47dd9 100644 --- a/webnotes/model/create_new.py +++ b/webnotes/model/create_new.py @@ -20,6 +20,8 @@ def get_new_doc(doctype, parent_doc = None, parentfield = None): meta = webnotes.get_doctype(doctype) + restrictions = webnotes.defaults.get_restrictions() + if parent_doc: doc.parent = parent_doc.name doc.parenttype = parent_doc.doctype @@ -29,7 +31,11 @@ def get_new_doc(doctype, parent_doc = None, parentfield = None): for d in meta.get({"doctype":"DocField", "parent": doctype}): default = webnotes.defaults.get_user_default(d.fieldname) - if default: + + if (d.fieldtype=="Link") and d.ignore_restrictions != 1 and (d.options in restrictions)\ + and len(restrictions[d.options])==1: + doc.fields[d.fieldname] = restrictions[d.options][0] + elif default: doc.fields[d.fieldname] = default elif d.fields.get("default"): if d.default == "__user": diff --git a/webnotes/widgets/form/load.py b/webnotes/widgets/form/load.py index 573c39e6bd..23d40d7404 100644 --- a/webnotes/widgets/form/load.py +++ b/webnotes/widgets/form/load.py @@ -28,6 +28,9 @@ def getdoc(doctype, name, user=None): try: bean = webnotes.bean(doctype, name) bean.run_method("onload") + + if not bean.has_read_perm(): + raise webnotes.PermissionError doclist = bean.doclist diff --git a/webnotes/widgets/reportview.py b/webnotes/widgets/reportview.py index 5b04cb604b..b06bf52d20 100644 --- a/webnotes/widgets/reportview.py +++ b/webnotes/widgets/reportview.py @@ -187,52 +187,49 @@ def build_filter_conditions(filters, conditions): def build_match_conditions(doctype, fields=None, as_condition=True): """add match conditions if applicable""" - match_filters = {} match_conditions = [] - match = True if not getattr(webnotes.local, "reportview_tables", None) \ or not getattr(webnotes.local, "reportview_doctypes", None): webnotes.local.reportview_tables = get_tables(doctype, fields) load_doctypes() - - if not getattr(webnotes.local, "reportview_roles", None): - webnotes.local.reportview_roles = webnotes.get_roles() - - for d in webnotes.local.reportview_doctypes[doctype]: - if d.doctype == 'DocPerm' and d.parent == doctype: - if d.role in webnotes.local.reportview_roles: - if d.match: # role applicable - if ':' in d.match: - document_key, default_key = d.match.split(":") - else: - default_key = document_key = d.match - for v in webnotes.defaults.get_user_default_as_list(default_key, \ - webnotes.session.user) or ["** No Match **"]: - if as_condition: - match_conditions.append('`tab%s`.%s="%s"' % (doctype, - document_key, v)) - else: - if v: - match_filters.setdefault(document_key, []) - if v not in match_filters[document_key]: - match_filters[document_key].append(v) - - elif d.read == 1 and d.permlevel == 0: - # don't restrict if another read permission at level 0 - # exists without a match restriction - match = False - match_filters = {} - if as_condition: - conditions = "" - if match_conditions and match: - conditions = '('+ ' or '.join(match_conditions) +')' + # get restrictions + restrictions = webnotes.defaults.get_restrictions() + + if not restrictions: + return "" if as_condition else {} + + fields_to_check = webnotes.local.reportview_doctypes[doctype].get({"doctype":"DocField", + "fieldtype":"Link", "parent":doctype, + "ignore_restriction":("!=", 1), + "options":("in", restrictions.keys())}) + if doctype in restrictions: + fields_to_check.append(webnotes._dict({"fieldname":"name", "options":doctype})) + # check in links + for df in fields_to_check: + if as_condition: + match_conditions.append('`tab{doctype}`.{fieldname} in ({values})'.format(doctype=doctype, + fieldname=df.fieldname, + values=", ".join([('"'+v.replace('"', '\"')+'"') for v in restrictions[df.options]]))) + else: + match_filters.setdefault(df.fieldname, []) + match_filters[df.fieldname]= restrictions[df.options] + + # add owner match + if webnotes.local.reportview_doctypes[doctype].get({"doctype":"DocPerm","read":1, + "permlevel":0,"match":"owner"}): + match_conditions.append('`tab{doctype}.owner="{user}"`'.format(doctye=doctype, + owner=webnotes.session.user)) + match_filters["owner"] = [webnotes.session.user] + + if as_condition: + conditions = " and ".join(match_conditions) doctype_conditions = get_doctype_conditions(doctype) if doctype_conditions: - conditions += ' and ' + doctype_conditions if conditions else doctype_conditions + conditions += ' and ' + doctype_conditions if conditions else doctype_conditions return conditions else: return match_filters