[permission model] Redesign

This commit is contained in:
Rushabh Mehta 2013-11-26 17:54:46 +05:30 committed by Anand Doshi
parent ac4d34d635
commit 202cce4b34
14 changed files with 138 additions and 179 deletions

View file

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

View file

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

View file

@ -41,27 +41,12 @@ wn.pages['permission-manager'].onload = function(wrapper) {
<h4><i class='icon-user'></i> "+wn._("Restricting By User")+":</h4>\
<ol>\
<li>"+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'")+".</li>\
<li>"+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.")+
"</ol>\
wn._("Click on button in the 'Condition' column and select the option 'User is the creator of the document'")+".</li></ol>\
</tr></td>\
<tr><td>\
<h4><i class='icon-cog'></i> "+wn._("Advanced Settings")+":</h4>\
<p>"+wn._("To further restrict permissions based on certain values in a document, use the 'Condition' settings.")+" <br><br>"+
wn._("For example: You want to restrict users to transactions marked with a certain property called 'Territory'")+":</p>\
<ol>\
<li>"+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")+
"<a href='#List/Custom Field'>"+wn._("Custom Field")+"</a>"+ wn._("of type Link")+".</li>\
<li>"+wn._("In the Permission Manager, click on the button in the 'Condition' column for the Role you want to restrict.")+"</li>\
<li>"+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")+".</li>\
<li>"+wn._("Go to Setup > <a href='#user-properties'>User Properties</a> to set \
'territory' for diffent Users.")+"</li>\
</ol>\
<p>"+wn._("Once you have set this, the users will only be able access documents with that property.")+"</p>\
<hr>\
<p>"+wn._("To further restrict permissions based on certain values, like Company or Territory in a document, please go to <a href='#user-properties'>User Restrictions</a>")+" <br><br>"+
"<p>"+wn._("Once you have set this, the users will only be able access documents where the link (e.g Company) exists.")+"</p><hr>\
<p>"+wn._("If these instructions where not helpful, please add in your suggestions at <a href='https://github.com/webnotes/wnframework/issues'>GitHub Issues</a>")+"</p>\
</tr></td>\
</table>");
@ -390,23 +375,6 @@ wn.PermissionEngine = Class.extend({
<input name='perm-rule' type='radio' value='owner'> The user is the creator of the document.\
</label>").css("padding", "15px");
// profile fields
$.each(me.get_profile_fields(perm.parent), function(i, d) {
$("<label class='radio'>\
<input name='perm-rule' type='radio' value='"+d.fieldname
+":user'>Value of field <b>"+d.label+"</b> is the User.\
</label>").appendTo(dialog.body);
});
// add options for all link fields
$.each(me.get_link_fields(perm.parent), function(i, d) {
$("<label class='radio'>\
<input name='perm-rule' type='radio' value='"+d.fieldname
+"'><b>"+d.label+"</b> in <b>"+d.parent+"</b> matches <a href='#user-properties//"+d.fieldname+"'>User Property</a> <b>"
+d.fieldname+"</b>.\
</label>").appendTo(dialog.body);
});
// button
$("<button class='btn btn-default btn-info'>Update</button>")
.appendTo($("<p>").appendTo(dialog.body))

View file

@ -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("<div class='user-settings'></div>\
$(wrapper).find(".layout-main").html("<div class='user-settings' style='min-height: 200px;'></div>\
<table class='table table-bordered' style='background-color: #f9f9f9;'>\
<tr><td>\
<h4><i class='icon-question-sign'></i> "+wn._("Quick Help for User Properties")+":</h4>\
<h4><i class='icon-question-sign'></i> "+wn._("Quick Help for Permission Restrictions")+":</h4>\
<ol>\
<li>"+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.")+"</li>\
<li>"+wn._("These properties are Link Type fields from all Documents.")+"</li>\
<li>"+wn._("These properties will appear as values in forms that contain them.")+"</li>\
<li>"+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 <a href='#permission-manager'>Permission Manager</a>")+"</li>\
<li>"+wn._("A user can have multiple values for a property.")+"</li>\
<li>"+wn._("Apart from the existing Permission Rules, you can apply addition restriction based on Type.")+"</li>\
<li>"+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.")+"</li>\
<li>"+wn._("These will also be set as default values for those links.")+"</li>\
<li>"+wn._("A user can be restricted to multiple records of the same type.")+"</li>\
</ol>\
</tr></td>\
</table>");
@ -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({
<tbody></tbody>\
</table>").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) {
$("<th>").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;
$("<button class='btn btn-info'>"+wn._("Add A Property")+"</button>")
$("<button class='btn btn-info'>"+wn._("Add A Restriction")+"</button>")
.appendTo($("<p>").appendTo(this.body))
.click(function() {
var d = new wn.ui.Dialog({
@ -229,6 +227,5 @@ wn.UserProperties = Class.extend({
});
d.show();
});
}
})

View file

@ -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,))
webnotes.add_default(defkey, defvalue, parent, "Restriction")

View file

@ -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]) {
$("<img src='"+me.frm.doc[me.df.options]+"' style='max-width: 70%;'>")
.appendTo(me.$wrapper);
} else {
$("<div class='missing-image'><i class='icon-camera'></i></div>")
.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]) {
$("<img src='"+me.frm.doc[me.df.options]+"' style='max-width: 70%;'>")
.appendTo(me.$wrapper);
} else {
$("<div class='missing-image'><i class='icon-camera'></i></div>")
.appendTo(me.$wrapper)
}
return false;
});
}
});

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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