diff --git a/core/doctype/doctype/doctype.py b/core/doctype/doctype/doctype.py index 9e7629f544..5bbe6b093e 100644 --- a/core/doctype/doctype/doctype.py +++ b/core/doctype/doctype/doctype.py @@ -72,7 +72,8 @@ class DocType: webnotes.msgprint(c + " not allowed in name", raise_exception=1) self.validate_series() self.scrub_field_names() - validate_fields(filter(lambda d: d.doctype=="DocField", self.doclist)) + validate_fields(self.doclist.get({"doctype":"DocField"})) + validate_permissions(self.doclist.get({"doctype":"DocPerm"})) self.set_version() def on_update(self): @@ -165,9 +166,9 @@ class DocType: def validate_fields_for_doctype(doctype): from webnotes.model.doctype import get - validate_fields(filter(lambda d: d.doctype=="DocField" and d.parent==doctype, - get(doctype, cached=False))) - + validate_fields(get(doctype, cached=False).get({"parent":doctype, + "doctype":"DocField"})) + def validate_fields(fields): def check_illegal_characters(fieldname): for c in ['.', ',', ' ', '-', '&', '%', '=', '"', "'", '*', '$', @@ -211,4 +212,62 @@ def validate_fields(fields): check_illegal_mandatory(d) check_link_table_options(d) check_hidden_and_mandatory(d) + +def validate_permissions_for_doctype(doctype, for_remove=False): + from webnotes.model.doctype import get + validate_permissions(get(doctype, cached=False).get({"parent":doctype, + "doctype":"DocPerm"}), for_remove) + +def validate_permissions(permissions, for_remove=False): + def get_txt(d): + return "For %s (level %s) in %s:" % (d.role, d.permlevel, d.parent) + + 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: + webnotes.msgprint(get_txt(d) + " Atleast one of Read, Write, Create, Submit, Cancel must be set.", + raise_exception=True) + + def check_double(d): + similar = permissions.get({ + "role":d.role, + "permlevel":d.permlevel, + "match": d.match + }) + + if len(similar) > 1: + webnotes.msgprint(get_txt(d) + " Only one rule allowed for a particular Role and Level.", + raise_exception=True) + + def check_level_zero_is_set(d): + if d.permlevel > 0 and d.role != 'All': + if not permissions.get({"role": d.role, "permlevel": 0}): + webnotes.msgprint(get_txt(d) + " Higher level permissions are meaningless if level 0 permission is not set.", + raise_exception=True) + + if d.create: + webnotes.msgprint("Create Permission has no meaning at level " + d.permlevel, + raise_exception=True) + + if d.submit: + webnotes.msgprint("Submit Permission has no meaning at level " + d.permlevel, + raise_exception=True) + + if d.cancel: + webnotes.msgprint("Cancel Permission has no meaning at level " + d.permlevel, + raise_exception=True) + + if d.amend: + webnotes.msgprint("Amend Permission has no meaning at level " + d.permlevel, + raise_exception=True) + + if d.match: + webnotes.msgprint("Match rules have no meaning at level " + d.permlevel, + raise_exception=True) + + for d in permissions: + if not d.permlevel: d.permlevel=0 + check_atleast_one_set(d) + if not for_remove: + check_double(d) + check_level_zero_is_set(d) \ No newline at end of file diff --git a/core/page/permission_manager/__init__.py b/core/page/permission_manager/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/core/page/permission_manager/permission_manager.js b/core/page/permission_manager/permission_manager.js new file mode 100644 index 0000000000..834112abac --- /dev/null +++ b/core/page/permission_manager/permission_manager.js @@ -0,0 +1,445 @@ +wn.pages['permission-manager'].onload = function(wrapper) { + wn.ui.make_app_page({ + parent: wrapper, + title: 'Permission Manager', + single_column: true + }); + wrapper.permission_engine = new wn.PermissionEngine(wrapper); +} +wn.pages['permission-manager'].refresh = function(wrapper) { + wrapper.permission_engine.set_from_route(); +} + +wn.PermissionEngine = Class.extend({ + init: function(wrapper) { + this.wrapper = wrapper; + this.body = $(this.wrapper).find(".layout-main"); + this.make(); + this.refresh(); + this.add_check_events(); + }, + make: function() { + var me = this; + + me.make_reset_button(); + wn.call({ + module:"core", + page:"permission_manager", + method: "get_roles_and_doctypes", + callback: function(r) { + me.options = r.message; + me.doctype_select + = me.wrapper.appframe.add_select("doctypes", + ["Select Document Type..."].concat(r.message.doctypes)) + .css("width", "200px") + .change(function() { + wn.set_route("permission-manager", $(this).val()) + }); + me.role_select + = me.wrapper.appframe.add_select("roles", + ["Select Role..."].concat(r.message.roles)) + .css("width", "200px") + .change(function() { + me.refresh(); + }); + me.set_from_route(); + } + }); + }, + set_from_route: function() { + if(wn.get_route()[1] && this.doctype_select) { + this.doctype_select.val(wn.get_route()[1]); + this.refresh(); + } else { + this.refresh(); + } + }, + make_reset_button: function() { + var me = this; + me.reset_button = me.wrapper.appframe.add_button("Reset Permissions", function() { + if(wn.confirm("Reset Permissions for " + me.get_doctype() + "?", function() { + wn.call({ + module:"core", + page:"permission_manager", + method:"reset", + args: { + doctype:me.get_doctype(), + }, + callback: function() { me.refresh(); } + }); + })); + }, 'icon-retweet'); + }, + get_doctype: function() { + var doctype = this.doctype_select.val(); + return doctype=="Select Document Type..." ? null : doctype; + }, + get_role: function() { + var role = this.role_select.val(); + return role=="Select Role..." ? null : role; + }, + refresh: function() { + var me = this; + if(!me.doctype_select) { + this.body.html("
").appendTo(this.body)) + .click(function() { + var d = new wn.ui.Dialog({ + title: "Add New Permission Rule", + fields: [ + {fieldtype:"Select", label:"Document Type", + options:me.options.doctypes, reqd:1, fieldname:"parent"}, + {fieldtype:"Select", label:"Role", + options:me.options.roles, reqd:1}, + {fieldtype:"Select", label:"Permission Level", + options:[0,1,2,3,4,5,6,7,8,9], reqd:1, fieldname: "permlevel", + description:"Level 0 is for document level permissions, higher levels for field level permissions."}, + {fieldtype:"Button", label:"Add"}, + ] + }); + if(me.get_doctype()) { + d.set_value("parent", me.get_doctype()); + d.get_input("parent").attr("disabled", true); + } + if(me.get_role()) { + d.set_value("role", me.get_role()); + d.get_input("role").attr("disabled", true); + } + d.set_value("permlevel", "0"); + d.get_input("add").click(function() { + var args = d.get_values(); + if(!args) { + return; + } + wn.call({ + module: "core", + page: "permission_manager", + method: "add", + args: args, + callback: function(r) { + if(r.exc) { + msgprint("Did not add."); + } else { + me.refresh(); + } + } + }) + d.hide(); + }); + d.show(); + }); + }, + get_perm: function(name) { + return $.map(this.perm_list, function(d) { if(d.name==name) return d; })[0]; + }, + show_match_manager:function(name) { + var perm = this.get_perm(name); + var me = this; + + wn.model.with_doctype(perm.parent, function() { + var dialog = new wn.ui.Dialog({ + title: "Applies for Users", + }); + $(dialog.body).html("
").appendTo(dialog.body)) + .attr("data-name", perm.name) + .click(function() { + var match_value = $(dialog.wrapper).find(":radio:checked").val(); + var perm = me.get_perm($(this).attr('data-name')) + wn.call({ + module: "core", + page: "permission_manager", + method: "update_match", + args: { + name: perm.name, + doctype: perm.parent, + match: match_value + }, + callback: function() { + dialog.hide(); + me.refresh(); + } + }) + }); + dialog.show(); + + // select + if(perm.match) { + $(dialog.wrapper).find("[value='"+perm.match+"']").attr("checked", "checked").focus(); + } else { + $(dialog.wrapper).find('[value=""]').attr("checked", "checked").focus(); + } + }); + }, + get_profile_fields: function(doctype) { + var profile_fields = wn.model.get("DocField", {parent:doctype, + fieldtype:"Link", options:"Profile"}); + + profile_fields = profile_fields.concat(wn.model.get("DocField", {parent:doctype, + fieldtype:"Select", link_doctype:"Profile"})) + + return profile_fields + }, + get_link_fields: function(doctype) { + return link_fields = wn.model.get("DocField", {parent:doctype, + fieldtype:"Link", options:["not in", ["Profile", '[Select]']]}); + }, + show_explain: function() { + $(".perm-explain").remove(); + if(!this.get_doctype()) return; + var wrapper = $("
").appendTo(this.body); + var doctype = null; + var core_finished = false; + $.each(this.perm_list, function(i, p) { + if(p.parent != doctype) { + core_finished = false; + doctype = p.parent; + $('').html("A user with role "+p.role + " can " + + perms + " a document of type " + doctype + ".") + + } else { + if(p.match=="owner") { + var _p = $('
').html("A user with role "+p.role + " can " + + perms + " " + doctype + " only if that document is created by that user."); + } else if(p.match.indexOf(":")!=-1) { + var field = p.match.split(":")[0]; + var _p = $('
').html("A user with role "+p.role + " can " + + perms + " " + doctype + " only if "+field+" equals User's Id."); + } else { + var _p = $('
').html("A user with role "+p.role + " can "
+ + perms + " " + doctype + " only for records with user's "+p.match+" property.");
+ }
+ }
+
+ } else {
+ if(!core_finished) {
+ core_finished = true;
+ $("
').html("A user with role "+p.role + " can only " + + perms + " fields at level "+ p.permlevel +" in a " + doctype + ".") + } + + $("Show Users").appendTo(_p).attr("data-role", p.role) + .css("margin-left", "7px") + .click(function() { + var link = $(this); + wn.call({ + module: "core", + page: "permission_manager", + method: "get_users_with_role", + args: { + role: link.attr("data-role"), + }, + callback: function(r) { + $.each(r.message, function(i, uid) { + msgprint("" + + wn.user_info(uid).fullname + " ("+ + uid+")"); + }); + cur_dialog.set_title("Users with role " + + link.attr("data-role")); + } + }); + }); + _p.appendTo(wrapper); + }) + } +}) \ No newline at end of file diff --git a/core/page/permission_manager/permission_manager.py b/core/page/permission_manager/permission_manager.py new file mode 100644 index 0000000000..f44bdb5092 --- /dev/null +++ b/core/page/permission_manager/permission_manager.py @@ -0,0 +1,72 @@ +from __future__ import unicode_literals +import webnotes + +@webnotes.whitelist(allow_roles=["System Manager", "Administrator"]) +def get_roles_and_doctypes(): + return { + "doctypes": [d[0] for d in webnotes.conn.sql("""select name from tabDocType where + ifnull(istable,0)=0 and + ifnull(issingle,0)=0 and + module != 'Core' """)], + "roles": [d[0] for d in webnotes.conn.sql("""select name from tabRole where name not in + ('All', 'Guest', 'Administrator')""")] + } + +@webnotes.whitelist(allow_roles=["System Manager", "Administrator"]) +def get_permissions(doctype=None, role=None): + return webnotes.conn.sql("""select * from tabDocPerm + where %s%s order by parent, permlevel, role""" % (\ + doctype and (" parent='%s'" % doctype) or "", + role and ((doctype and " and " or "") + " role='%s'" % role) or "", + ), as_dict=True) + +@webnotes.whitelist(allow_roles=["System Manager", "Administrator"]) +def remove(doctype, name): + webnotes.conn.sql("""delete from tabDocPerm where name=%s""", name) + validate_and_reset(doctype, for_remove=True) + +@webnotes.whitelist(allow_roles=["System Manager", "Administrator"]) +def add(parent, role, permlevel): + webnotes.doc(fielddata={ + "doctype":"DocPerm", + "__islocal": 1, + "parent": parent, + "parenttype": "DocType", + "parentfield": "permissions", + "role": role, + "permlevel": permlevel, + "read": 1 + }).save() + + validate_and_reset(parent) + +@webnotes.whitelist(allow_roles=["System Manager", "Administrator"]) +def update(name, doctype, ptype, value=0): + webnotes.conn.sql("""update tabDocPerm set `%s`=%s where name=%s"""\ + % (ptype, '%s', '%s'), (value, name)) + validate_and_reset(doctype) + +@webnotes.whitelist(allow_roles=["System Manager", "Administrator"]) +def update_match(name, doctype, match=""): + webnotes.conn.sql("""update tabDocPerm set `match`=%s where name=%s""", + (match, name)) + validate_and_reset(doctype) + +def validate_and_reset(doctype, for_remove=False): + from core.doctype.doctype.doctype import validate_permissions_for_doctype + validate_permissions_for_doctype(doctype, for_remove) + webnotes.clear_cache(doctype=doctype) + +@webnotes.whitelist(allow_roles=["System Manager", "Administrator"]) +def reset(doctype): + webnotes.reset_perms(doctype) + webnotes.clear_cache(doctype=doctype) + +@webnotes.whitelist(allow_roles=["System Manager", "Administrator"]) +def get_users_with_role(role): + return [p[0] for p in webnotes.conn.sql("""select distinct tabProfile.name + from tabUserRole, tabProfile where + tabUserRole.role=%s + and tabProfile.name != "Administrator" + and tabUserRole.parent = tabProfile.name + and ifnull(tabProfile.enabled,0)=1""", role)] diff --git a/core/page/permission_manager/permission_manager.txt b/core/page/permission_manager/permission_manager.txt new file mode 100644 index 0000000000..4aadd6af7c --- /dev/null +++ b/core/page/permission_manager/permission_manager.txt @@ -0,0 +1,21 @@ +[ + { + "owner": "Administrator", + "docstatus": 0, + "creation": "2013-01-01 11:00:01", + "modified_by": "Administrator", + "modified": "2013-01-01 11:00:01" + }, + { + "name": "__common__", + "title": "Permission Manager", + "doctype": "Page", + "module": "Core", + "standard": "Yes", + "page_name": "Permission Manager" + }, + { + "name": "permission-manager", + "doctype": "Page" + } +] \ No newline at end of file diff --git a/core/page/user_properties/__init__.py b/core/page/user_properties/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/core/page/user_properties/user_properties.js b/core/page/user_properties/user_properties.js new file mode 100644 index 0000000000..84f54added --- /dev/null +++ b/core/page/user_properties/user_properties.js @@ -0,0 +1,213 @@ +wn.pages['user-properties'].onload = function(wrapper) { + wn.ui.make_app_page({ + parent: wrapper, + title: 'User Properties', + single_column: true + }); + wrapper.user_properties = new wn.UserProperties(wrapper); +} + +wn.pages['user-properties'].refresh = function(wrapper) { + wrapper.user_properties.set_from_route(); +} + +wn.UserProperties = Class.extend({ + init: function(wrapper) { + this.wrapper = wrapper; + this.body = $(this.wrapper).find(".layout-main"); + this.make(); + this.refresh(); + }, + make: function() { + var me = this; + wn.call({ + module:"core", + page:"user_properties", + method: "get_users_and_links", + callback: function(r) { + me.options = r.message; + me.user_select = + me.wrapper.appframe.add_select("users", + ["Select User..."].concat(r.message.users)) + .css("width", "200px") + .change(function() { + wn.set_route("user-properties", $(this).val()) + }); + me.property_select = + me.wrapper.appframe.add_select("links", + ["Select Property..."].concat(me.get_link_names())) + .css("width", "200px") + .change(function() { + me.refresh(); + }); + me.set_from_route(); + } + }); + }, + get_link_names: function() { + return $.map(this.options.link_fields, function(l) { return l[0]; }); + }, + set_from_route: function() { + if(wn.get_route()[1] && this.user_select) { + this.user_select.val(wn.get_route()[1]); + this.refresh(); + } else { + this.refresh(); + } + }, + get_user: function() { + var user = this.user_select.val(); + return user=="Select User..." ? null : user; + }, + get_property: function() { + var property = this.property_select.val(); + return property=="Select Property..." ? null : property; + }, + render: function(prop_list) { + this.body.empty(); + this.prop_list = prop_list; + if(!prop_list.length) { + this.body.html("
").appendTo(this.body)) + .click(function() { + var d = new wn.ui.Dialog({ + title: "Add New Property", + fields: [ + {fieldtype:"Select", label:"User", + options:me.options.users, reqd:1, fieldname:"parent"}, + {fieldtype:"Select", label:"Property", fieldname:"defkey", + options:me.get_link_names(), reqd:1}, + {fieldtype:"Link", label:"Value", fieldname:"defvalue", + options:'[Select]', reqd:1}, + {fieldtype:"Button", label:"Add"}, + ] + }); + if(me.get_user()) { + d.set_value("parent", me.get_user()); + d.get_input("parent").attr("disabled", true); + } + if(me.get_property()) { + d.set_value("defkey", me.get_property()); + d.get_input("defkey").attr("disabled", true); + } + d.fields_dict["defvalue"].get_query = function(txt) { + var key = d.get_value("defkey"); + var doctype = $.map(me.options.link_fields, function(l) { + if(l[0]==key) return l[1]; + })[0]; + return 'select name from `tab'+doctype + +'` where name like "%s"' + } + d.get_input("add").click(function() { + var args = d.get_values(); + if(!args) { + return; + } + wn.call({ + module: "core", + page: "user_properties", + method: "add", + args: args, + callback: function(r) { + if(r.exc) { + msgprint("Did not add."); + } else { + me.refresh(); + } + } + }) + d.hide(); + }); + d.show(); + }); + + } +}) \ No newline at end of file diff --git a/core/page/user_properties/user_properties.py b/core/page/user_properties/user_properties.py new file mode 100644 index 0000000000..aa56d19dac --- /dev/null +++ b/core/page/user_properties/user_properties.py @@ -0,0 +1,36 @@ +from __future__ import unicode_literals +import webnotes + +@webnotes.whitelist(allow_roles=["System Manager", "Administrator"]) +def get_users_and_links(): + links = list(set(webnotes.conn.sql("""select fieldname, options + from tabDocField where fieldtype='Link' + and parent not in ('[Select]', 'DocType', 'Module Def') + """) + webnotes.conn.sql("""select fieldname, options + from `tabCustom Field` where fieldtype='Link'"""))) + 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 + } + +@webnotes.whitelist(allow_roles=["System Manager", "Administrator"]) +def get_properties(user=None, key=None): + return webnotes.conn.sql("""select name, parent, defkey, defvalue + from tabDefaultValue + where %s%s and parent!='Control Panel' order by parent, defkey""" % (\ + user and (" parent='%s'" % user) or "", + key and ((user and " and " or "") + " defkey='%s'" % key) or "", + ), as_dict=True) + +@webnotes.whitelist(allow_roles=["System Manager", "Administrator"]) +def remove(user, name): + webnotes.conn.sql("""delete from tabDefaultValue where name=%s""", name) + webnotes.clear_cache(user=user) + +@webnotes.whitelist(allow_roles=["System Manager", "Administrator"]) +def add(parent, defkey, defvalue): + webnotes.conn.add_default(defkey, defvalue, parent) diff --git a/core/page/user_properties/user_properties.txt b/core/page/user_properties/user_properties.txt new file mode 100644 index 0000000000..e995fc0835 --- /dev/null +++ b/core/page/user_properties/user_properties.txt @@ -0,0 +1,21 @@ +[ + { + "owner": "Administrator", + "docstatus": 0, + "creation": "2013-01-01 18:50:55", + "modified_by": "Administrator", + "modified": "2013-01-01 18:50:55" + }, + { + "name": "__common__", + "title": "User Properties", + "doctype": "Page", + "module": "Core", + "standard": "Yes", + "page_name": "user-properties" + }, + { + "name": "user-properties", + "doctype": "Page" + } +] \ No newline at end of file diff --git a/public/js/wn/router.js b/public/js/wn/router.js index e74d6d1b1f..47fecb1c5a 100644 --- a/public/js/wn/router.js +++ b/public/js/wn/router.js @@ -86,6 +86,11 @@ $(window).bind('hashchange', function() { wn.route_titles[wn._cur_route] = document.title; if(window.location.hash==wn._cur_route) - return; + return; + + // hide open dialog + if(cur_dialog) + cur_dialog.hide(); + wn.route(); }); \ No newline at end of file diff --git a/public/js/wn/ui/field_group.js b/public/js/wn/ui/field_group.js index a0d46d95c8..bbdf7f6d61 100644 --- a/public/js/wn/ui/field_group.js +++ b/public/js/wn/ui/field_group.js @@ -113,5 +113,8 @@ wn.ui.FieldGroup = Class.extend({ f.set_input(f.df['default'] || ''); } } + }, + get_input: function(fieldname) { + return $(this.fields_dict[fieldname].input); } }); \ No newline at end of file diff --git a/webnotes/db.py b/webnotes/db.py index c61e69cf2e..1ef32dfaab 100644 --- a/webnotes/db.py +++ b/webnotes/db.py @@ -331,19 +331,23 @@ class Database: if self.sql("""select defkey from `tabDefaultValue` where defkey=%s and parent=%s """, (key, parent)): - # update self.sql("""update `tabDefaultValue` set defvalue=%s where parent=%s and defkey=%s""", (val, parent, key)) + webnotes.clear_cache() else: - from webnotes.model.doc import Document - d = Document('DefaultValue') - d.parent = parent - d.parenttype = 'Control Panel' # does not matter - d.parentfield = 'system_defaults' - d.defkey = key - d.defvalue = val - d.save(1) + self.add_default(key, val, parent) + + + def add_default(self, key, val, parent="Control Panel"): + d = webnotes.doc('DefaultValue') + d.parent = parent + d.parenttype = 'Control Panel' # does not matter + d.parentfield = 'system_defaults' + d.defkey = key + d.defvalue = val + d.save(1) + webnotes.clear_cache() def get_default(self, key, parent="Control Panel"): """get default value""" diff --git a/webnotes/model/doctype.py b/webnotes/model/doctype.py index e8196a6db8..9e785b2b84 100644 --- a/webnotes/model/doctype.py +++ b/webnotes/model/doctype.py @@ -291,6 +291,7 @@ def expand_selects(doclist): for d in filter(lambda d: d.fieldtype=='Select' \ and (d.options or '').startswith('link:'), doclist): doctype = d.options.split("\n")[0][5:] + d.link_doctype = doctype d.options = '\n'.join([''] + [o.name for o in webnotes.conn.sql("""select name from `tab%s` where docstatus<2 order by name asc""" % doctype, as_dict=1)])