Merge pull request #18877 from marination/get_role_permissions-js-consistency
fix: Correctness in `get_role_permissions` and `has_perm` JS APIs
This commit is contained in:
commit
d1d30e6451
4 changed files with 127 additions and 47 deletions
57
cypress/integration/permissions.js
Normal file
57
cypress/integration/permissions.js
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
context("Permissions API", () => {
|
||||
before(() => {
|
||||
cy.visit("/login");
|
||||
|
||||
cy.login("Administrator");
|
||||
cy.call("frappe.tests.ui_test_helpers.add_remove_role", {
|
||||
action: "remove",
|
||||
user: "frappe@example.com",
|
||||
role: "System Manager",
|
||||
});
|
||||
cy.call("logout");
|
||||
|
||||
cy.login("frappe@example.com");
|
||||
cy.visit("/app");
|
||||
});
|
||||
|
||||
it("Checks permissions via `has_perm` for Kanban Board DocType", () => {
|
||||
cy.visit("/app/kanban-board/view/list");
|
||||
cy.window()
|
||||
.its("frappe")
|
||||
.then((frappe) => {
|
||||
frappe.model.with_doctype("Kanban Board", function () {
|
||||
// needed to make sure doc meta is loaded
|
||||
expect(frappe.perm.has_perm("Kanban Board", 0, "read")).to.equal(true);
|
||||
expect(frappe.perm.has_perm("Kanban Board", 0, "write")).to.equal(true);
|
||||
expect(frappe.perm.has_perm("Kanban Board", 0, "print")).to.equal(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it("Checks permissions via `get_perm` for Kanban Board DocType", () => {
|
||||
cy.visit("/app/kanban-board/view/list");
|
||||
cy.window()
|
||||
.its("frappe")
|
||||
.then((frappe) => {
|
||||
frappe.model.with_doctype("Kanban Board", function () {
|
||||
// needed to make sure doc meta is loaded
|
||||
const perms = frappe.perm.get_perm("Kanban Board");
|
||||
expect(perms.read).to.equal(true);
|
||||
expect(perms.write).to.equal(true);
|
||||
expect(perms.rights_without_if_owner).to.include("read");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
after(() => {
|
||||
cy.call("logout");
|
||||
|
||||
cy.login("Administrator");
|
||||
cy.call("frappe.tests.ui_test_helpers.add_remove_role", {
|
||||
action: "add",
|
||||
user: "frappe@example.com",
|
||||
role: "System Manager",
|
||||
});
|
||||
cy.call("logout");
|
||||
});
|
||||
});
|
||||
|
|
@ -37,18 +37,23 @@ $.extend(frappe.perm, {
|
|||
|
||||
has_perm: (doctype, permlevel, ptype, doc) => {
|
||||
if (!permlevel) permlevel = 0;
|
||||
if (!frappe.perm.doctype_perm[doctype]) {
|
||||
frappe.perm.doctype_perm[doctype] = frappe.perm.get_perm(doctype, doc);
|
||||
}
|
||||
|
||||
let perms = frappe.perm.doctype_perm[doctype];
|
||||
|
||||
if (!perms || !perms[permlevel]) return false;
|
||||
|
||||
return !!perms[permlevel][ptype];
|
||||
const perms = frappe.perm.get_perm(doctype, doc);
|
||||
return !!perms?.[permlevel]?.[ptype];
|
||||
},
|
||||
|
||||
get_perm: (doctype, doc) => {
|
||||
// if document object is passed, get fresh doc based perms
|
||||
// (with ownership and user perms applied) else cached doctype perms
|
||||
|
||||
if (doc && !doc.__islocal) {
|
||||
return frappe.perm._get_perm(doctype, doc);
|
||||
}
|
||||
|
||||
return (frappe.perm.doctype_perm[doctype] ??= frappe.perm._get_perm(doctype));
|
||||
},
|
||||
|
||||
_get_perm: (doctype, doc) => {
|
||||
let perm = [{ read: 0, permlevel: 0 }];
|
||||
|
||||
let meta = frappe.get_doc("DocType", doctype);
|
||||
|
|
@ -61,77 +66,83 @@ $.extend(frappe.perm, {
|
|||
if (!meta) return perm;
|
||||
|
||||
perm = frappe.perm.get_role_permissions(meta);
|
||||
const base_perm = perm[0];
|
||||
|
||||
if (doc) {
|
||||
// apply user permissions via docinfo (which is processed server-side)
|
||||
let docinfo = frappe.model.get_docinfo(doctype, doc.name);
|
||||
if (docinfo && docinfo.permissions) {
|
||||
Object.keys(docinfo.permissions).forEach((ptype) => {
|
||||
perm[0][ptype] = docinfo.permissions[ptype];
|
||||
base_perm[ptype] = docinfo.permissions[ptype];
|
||||
});
|
||||
}
|
||||
|
||||
// if owner
|
||||
if (!$.isEmptyObject(perm[0].if_owner)) {
|
||||
if (doc.owner === user) {
|
||||
$.extend(perm[0], perm[0].if_owner);
|
||||
} else {
|
||||
// not owner, remove permissions
|
||||
$.each(perm[0].if_owner, (ptype) => {
|
||||
if (perm[0].if_owner[ptype]) {
|
||||
perm[0][ptype] = 0;
|
||||
}
|
||||
});
|
||||
if (doc.owner !== user) {
|
||||
for (const right of frappe.perm.rights) {
|
||||
if (base_perm[right] && !base_perm.rights_without_if_owner.has(right)) {
|
||||
base_perm[right] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// apply permissions from shared
|
||||
if (docinfo && docinfo.shared) {
|
||||
for (let i = 0; i < docinfo.shared.length; i++) {
|
||||
let s = docinfo.shared[i];
|
||||
if (s.user === user) {
|
||||
perm[0]["read"] = perm[0]["read"] || s.read;
|
||||
perm[0]["write"] = perm[0]["write"] || s.write;
|
||||
perm[0]["submit"] = perm[0]["submit"] || s.submit;
|
||||
perm[0]["share"] = perm[0]["share"] || s.share;
|
||||
for (const s of docinfo.shared) {
|
||||
if (s.user !== user) continue;
|
||||
|
||||
if (s.read) {
|
||||
// also give print, email permissions if read
|
||||
// and these permissions exist at level [0]
|
||||
perm[0].email =
|
||||
frappe.boot.user.can_email.indexOf(doctype) !== -1 ? 1 : 0;
|
||||
perm[0].print =
|
||||
frappe.boot.user.can_print.indexOf(doctype) !== -1 ? 1 : 0;
|
||||
}
|
||||
for (const right of ["read", "write", "submit", "share"]) {
|
||||
if (!base_perm[right]) base_perm[right] = s[right];
|
||||
}
|
||||
|
||||
if (s.read) {
|
||||
// also give print, email permissions if read
|
||||
// and these permissions exist at level [0]
|
||||
base_perm.email =
|
||||
frappe.boot.user.can_email.indexOf(doctype) !== -1 ? 1 : 0;
|
||||
base_perm.print =
|
||||
frappe.boot.user.can_print.indexOf(doctype) !== -1 ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (frappe.model.can_read(doctype) && !perm[0].read) {
|
||||
if (!base_perm.read && frappe.model.can_read(doctype)) {
|
||||
// read via sharing
|
||||
perm[0].read = 1;
|
||||
base_perm.read = 1;
|
||||
}
|
||||
|
||||
return perm;
|
||||
},
|
||||
|
||||
get_role_permissions: (meta) => {
|
||||
/** Returns a `dict` of evaluated Role Permissions like:
|
||||
{
|
||||
"read": 1,
|
||||
"write": 0,
|
||||
"rights_without_if_owner": {"read", "write"} // for permlevel 0
|
||||
}
|
||||
*/
|
||||
|
||||
let perm = [{ read: 0, permlevel: 0 }];
|
||||
// Returns a `dict` of evaluated Role Permissions
|
||||
|
||||
(meta.permissions || []).forEach((p) => {
|
||||
// if user has this role
|
||||
let permlevel = cint(p.permlevel);
|
||||
if (!perm[permlevel]) {
|
||||
perm[permlevel] = {};
|
||||
perm[permlevel]["permlevel"] = permlevel;
|
||||
const permlevel = cint(p.permlevel);
|
||||
const current_perm = (perm[permlevel] ??= { permlevel });
|
||||
|
||||
if (permlevel === 0) {
|
||||
current_perm.rights_without_if_owner ??= new Set();
|
||||
}
|
||||
|
||||
// if user has this role
|
||||
if (frappe.user_roles.includes(p.role)) {
|
||||
frappe.perm.rights.forEach((right) => {
|
||||
let value = perm[permlevel][right] || p[right] || 0;
|
||||
if (value) {
|
||||
perm[permlevel][right] = value;
|
||||
if (!p[right]) return;
|
||||
|
||||
current_perm[right] = 1;
|
||||
|
||||
if (permlevel === 0 && !p.if_owner) {
|
||||
current_perm.rights_without_if_owner.add(right);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -169,7 +180,8 @@ $.extend(frappe.perm, {
|
|||
}
|
||||
}
|
||||
|
||||
if (perm[0].if_owner && perm[0].read) {
|
||||
const base_perm = perm[0];
|
||||
if (base_perm.read && !base_perm.rights_without_if_owner.has("read")) {
|
||||
match_rules.push({ Owner: frappe.session.user });
|
||||
}
|
||||
return match_rules;
|
||||
|
|
|
|||
|
|
@ -579,3 +579,12 @@ def create_kanban():
|
|||
],
|
||||
}
|
||||
).insert()
|
||||
|
||||
|
||||
@whitelist_for_tests
|
||||
def add_remove_role(action, user, role):
|
||||
user_doc = frappe.get_doc("User", user)
|
||||
if action == "remove":
|
||||
user_doc.remove_roles(role)
|
||||
else:
|
||||
user_doc.add_roles(role)
|
||||
|
|
|
|||
|
|
@ -609,4 +609,6 @@ def get_link_options(web_form_name, doctype, allow_read_on_all_link_options=Fals
|
|||
return "\n".join([doc.value for doc in link_options])
|
||||
|
||||
else:
|
||||
raise frappe.PermissionError(_("You don't have permission to access the {0} DocType.").format(doctype))
|
||||
raise frappe.PermissionError(
|
||||
_("You don't have permission to access the {0} DocType.").format(doctype)
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue