fix: Role based access to workspace
This commit is contained in:
parent
e8e7211d62
commit
4a05697cfe
6 changed files with 108 additions and 87 deletions
|
|
@ -105,8 +105,8 @@ def load_conf_settings(bootinfo):
|
|||
if key in conf: bootinfo[key] = conf.get(key)
|
||||
|
||||
def load_desktop_data(bootinfo):
|
||||
from frappe.desk.doctype.workspace.workspace import get_pages
|
||||
bootinfo.allowed_workspaces = get_pages().get('pages')
|
||||
from frappe.desk.desktop import get_wspace_sidebar_items
|
||||
bootinfo.allowed_workspaces = get_wspace_sidebar_items().get('pages')
|
||||
bootinfo.module_page_map = get_controller("Workspace").get_module_page_map()
|
||||
bootinfo.dashboards = frappe.get_all("Dashboard")
|
||||
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@
|
|||
# Author - Shivam Mishra <shivam@frappe.io>
|
||||
|
||||
import frappe
|
||||
import json
|
||||
from json import loads, dumps
|
||||
from frappe import _, DoesNotExistError, ValidationError, _dict
|
||||
from frappe.boot import get_allowed_pages, get_allowed_reports
|
||||
from frappe.core.doctype.custom_role.custom_role import get_custom_allowed_roles
|
||||
from functools import wraps
|
||||
from frappe.cache_manager import (
|
||||
build_domain_restriced_doctype_cache,
|
||||
|
|
@ -35,13 +35,14 @@ class Workspace:
|
|||
self.extended_links = []
|
||||
self.extended_charts = []
|
||||
self.extended_shortcuts = []
|
||||
self.workspace_manager = "Workspace Manager" in frappe.get_roles()
|
||||
|
||||
self.user = frappe.get_user()
|
||||
self.allowed_modules = self.get_cached('user_allowed_modules', self.get_allowed_modules)
|
||||
|
||||
self.doc = self.get_page_for_user()
|
||||
self.doc = frappe.get_cached_doc("Workspace", self.page_name)
|
||||
|
||||
if self.doc.module and self.doc.module not in self.allowed_modules:
|
||||
if self.doc and self.doc.module and self.doc.module not in self.allowed_modules and not self.workspace_manager:
|
||||
raise frappe.PermissionError
|
||||
|
||||
self.can_read = self.get_cached('user_perm_can_read', self.get_can_read_items)
|
||||
|
|
@ -58,8 +59,8 @@ class Workspace:
|
|||
self.restricted_pages = frappe.cache().get_value("domain_restricted_pages") or build_domain_restriced_page_cache()
|
||||
|
||||
def is_page_allowed(self):
|
||||
cards = self.doc.get_link_groups() + get_custom_reports_and_doctypes(self.doc.module) + self.extended_links
|
||||
shortcuts = self.doc.shortcuts + self.extended_shortcuts
|
||||
cards = self.doc.get_link_groups() + get_custom_reports_and_doctypes(self.doc.module)
|
||||
shortcuts = self.doc.shortcuts
|
||||
|
||||
for section in cards:
|
||||
links = loads(section.get('links')) if isinstance(section.get('links'), str) else section.get('links')
|
||||
|
|
@ -77,8 +78,28 @@ class Workspace:
|
|||
if self.is_item_allowed(item.link_to, item.type) and _in_active_domains(item):
|
||||
return True
|
||||
|
||||
if not shortcuts and not self.doc.links:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def is_permitted(self):
|
||||
"""Returns true if Has Role is not set or the user is allowed."""
|
||||
from frappe.utils import has_common
|
||||
|
||||
allowed = [d.role for d in frappe.get_all("Has Role", fields=["role"], filters={"parent": self.doc.name})]
|
||||
|
||||
custom_roles = get_custom_allowed_roles('page', self.doc.name)
|
||||
allowed.extend(custom_roles)
|
||||
|
||||
if not allowed:
|
||||
return True
|
||||
|
||||
roles = frappe.get_roles()
|
||||
|
||||
if has_common(roles, allowed):
|
||||
return True
|
||||
|
||||
def get_cached(self, cache_key, fallback_fn):
|
||||
_cache = frappe.cache()
|
||||
|
||||
|
|
@ -104,24 +125,6 @@ class Workspace:
|
|||
|
||||
return self.user.allow_modules
|
||||
|
||||
def get_page_for_user(self):
|
||||
if self.public_page:
|
||||
filters = {
|
||||
'label': self.page_name,
|
||||
'public': 1
|
||||
}
|
||||
public_pages = frappe.get_all("Workspace", filters=filters, limit=1)
|
||||
if public_pages:
|
||||
return frappe.get_cached_doc("Workspace", public_pages[0])
|
||||
|
||||
filters = {
|
||||
'label': self.page_title + "-" + frappe.session.user,
|
||||
'for_user': frappe.session.user
|
||||
}
|
||||
user_pages = frappe.get_all("Workspace", filters=filters, limit=1)
|
||||
if user_pages:
|
||||
return frappe.get_cached_doc("Workspace", user_pages[0])
|
||||
|
||||
def get_onboarding_doc(self):
|
||||
# Check if onboarding is enabled
|
||||
if not frappe.get_system_settings("enable_onboarding"):
|
||||
|
|
@ -372,39 +375,45 @@ def get_desktop_page(page):
|
|||
return {}
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_desk_sidebar_items():
|
||||
def get_wspace_sidebar_items():
|
||||
"""Get list of sidebar items for desk"""
|
||||
has_access = "Workspace Manager" in frappe.get_roles()
|
||||
|
||||
# don't get domain restricted pages
|
||||
blocked_modules = frappe.get_doc('User', frappe.session.user).get_blocked_modules()
|
||||
blocked_modules.append('Dummy Module')
|
||||
|
||||
filters = {
|
||||
'restrict_to_domain': ['in', frappe.get_active_domains()],
|
||||
'extends_another_page': 0,
|
||||
'for_user': '',
|
||||
'module': ['not in', blocked_modules]
|
||||
}
|
||||
|
||||
if not frappe.local.conf.developer_mode:
|
||||
filters['developer_mode_only'] = '0'
|
||||
if has_access:
|
||||
filters = []
|
||||
|
||||
# pages sorted based on pinned to top and then by name
|
||||
order_by = "pin_to_top desc, pin_to_bottom asc, name asc"
|
||||
all_pages = frappe.get_all("Workspace", fields=["name", "title", "public", "module", "icon"],
|
||||
filters=filters, order_by=order_by, ignore_permissions=True)
|
||||
# pages sorted based on sequence id
|
||||
order_by = "sequence_id asc"
|
||||
fields = ["name", "title", "for_user", "parent_page", "content", "public", "module", "icon"]
|
||||
all_pages = frappe.get_all("Workspace", fields=fields, filters=filters, order_by=order_by, ignore_permissions=True)
|
||||
pages = []
|
||||
private_pages = []
|
||||
|
||||
# Filter Page based on Permission
|
||||
for page in all_pages:
|
||||
try:
|
||||
wspace = Workspace(page)
|
||||
if wspace.is_page_allowed():
|
||||
pages.append(page)
|
||||
if wspace.is_permitted() and wspace.is_page_allowed() or has_access:
|
||||
if page.public:
|
||||
pages.append(page)
|
||||
elif page.for_user == frappe.session.user:
|
||||
private_pages.append(page)
|
||||
page['label'] = _(page.get('name'))
|
||||
except frappe.PermissionError:
|
||||
pass
|
||||
if private_pages:
|
||||
pages.extend(private_pages)
|
||||
|
||||
return pages
|
||||
return {'pages': pages, 'has_access': has_access}
|
||||
|
||||
def get_table_with_counts():
|
||||
counts = frappe.cache().get_value("information_schema:counts")
|
||||
|
|
@ -575,7 +584,7 @@ def clean_up(original_page, blocks):
|
|||
|
||||
for wid in ['shortcut', 'card', 'chart']:
|
||||
# get list of widget's name from blocks
|
||||
page_widgets[wid] = [x['data'][wid + '_name'] for x in json.loads(blocks) if x['type'] == wid]
|
||||
page_widgets[wid] = [x['data'][wid + '_name'] for x in loads(blocks) if x['type'] == wid]
|
||||
|
||||
# shortcut & chart cleanup
|
||||
for wid in ['shortcut', 'chart']:
|
||||
|
|
|
|||
|
|
@ -38,7 +38,8 @@
|
|||
"shortcuts",
|
||||
"section_break_18",
|
||||
"cards_label",
|
||||
"links"
|
||||
"links",
|
||||
"roles"
|
||||
],
|
||||
"fields": [
|
||||
{
|
||||
|
|
@ -252,10 +253,16 @@
|
|||
"fieldname": "sequence_id",
|
||||
"fieldtype": "Int",
|
||||
"label": "Sequence Id"
|
||||
},
|
||||
{
|
||||
"fieldname": "roles",
|
||||
"fieldtype": "Table",
|
||||
"label": "Roles",
|
||||
"options": "Has Role"
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"modified": "2021-07-05 17:41:36.272294",
|
||||
"modified": "2021-07-15 14:20:37.623926",
|
||||
"modified_by": "Administrator",
|
||||
"module": "Desk",
|
||||
"name": "Workspace",
|
||||
|
|
@ -269,7 +276,7 @@
|
|||
"print": 1,
|
||||
"read": 1,
|
||||
"report": 1,
|
||||
"role": "System Manager",
|
||||
"role": "Workspace Manager",
|
||||
"share": 1,
|
||||
"write": 1
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
# For license information, please see license.txt
|
||||
|
||||
import frappe
|
||||
import json
|
||||
from frappe import _
|
||||
from frappe.modules.export_file import export_to_files
|
||||
from frappe.model.document import Document
|
||||
|
|
@ -26,8 +25,8 @@ class Workspace(Document):
|
|||
frappe.throw(_("You can only have one default page that extends a particular standard page."))
|
||||
|
||||
def on_update(self):
|
||||
# if disable_saving_as_standard():
|
||||
# return
|
||||
if disable_saving_as_standard():
|
||||
return
|
||||
|
||||
if frappe.conf.developer_mode and self.module and self.public:
|
||||
export_to_files(record_list=[['Workspace', self.name]], record_module=self.module)
|
||||
|
|
@ -159,20 +158,7 @@ def get_report_type(report):
|
|||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_pages():
|
||||
has_access = "System Manager" in frappe.get_roles()
|
||||
fields = ['name', 'title', 'icon', 'public', 'parent_page', 'content']
|
||||
|
||||
pages = get_page_list(fields, {'public': 1})
|
||||
private_pages = get_page_list(fields, {'for_user': frappe.session.user})
|
||||
|
||||
if private_pages:
|
||||
pages.extend(private_pages)
|
||||
|
||||
return {'pages': pages, 'has_access': has_access}
|
||||
|
||||
@frappe.whitelist()
|
||||
def save_page(title, parent, public, sb_items, deleted_pages, new_widgets, blocks, save):
|
||||
def save_page(title, parent, public, sb_public_items, sb_private_items, deleted_pages, new_widgets, blocks, save):
|
||||
save = frappe.parse_json(save)
|
||||
public = frappe.parse_json(public)
|
||||
if save:
|
||||
|
|
@ -206,20 +192,20 @@ def save_page(title, parent, public, sb_items, deleted_pages, new_widgets, block
|
|||
doc.content = blocks
|
||||
doc.save(ignore_permissions=True)
|
||||
|
||||
if json.loads(new_widgets):
|
||||
if loads(new_widgets):
|
||||
save_new_widget(doc, title, blocks, new_widgets)
|
||||
|
||||
if json.loads(sb_items):
|
||||
sort_pages(json.loads(sb_items))
|
||||
if loads(sb_public_items) or loads(sb_private_items):
|
||||
sort_pages(loads(sb_public_items), loads(sb_private_items))
|
||||
|
||||
if json.loads(deleted_pages):
|
||||
return delete_pages(json.loads(deleted_pages))
|
||||
if loads(deleted_pages):
|
||||
return delete_pages(loads(deleted_pages))
|
||||
|
||||
return {"name": title, "public": public}
|
||||
|
||||
def delete_pages(deleted_pages):
|
||||
for page in deleted_pages:
|
||||
if page.get("public") and "System Manager" not in frappe.get_roles():
|
||||
if page.get("public") and "Workspace Manager" not in frappe.get_roles():
|
||||
return {"name": page.get("title"), "public": 1}
|
||||
|
||||
if frappe.db.exists("Workspace", page.get("name")):
|
||||
|
|
@ -227,17 +213,15 @@ def delete_pages(deleted_pages):
|
|||
|
||||
return {"name": "Home", "public": 1}
|
||||
|
||||
def sort_pages(sb_items):
|
||||
public_pages = [page for page in sb_items if page.get('public')=='1']
|
||||
private_pages = [page for page in sb_items if page.get('public')=='0']
|
||||
|
||||
def sort_pages(sb_public_items, sb_private_items):
|
||||
wspace_public_pages = get_page_list(['name', 'title'], {'public': 1})
|
||||
wspace_private_pages = get_page_list(['name', 'title'], {'for_user': frappe.session.user})
|
||||
|
||||
sort_page(wspace_private_pages, private_pages)
|
||||
if sb_private_items:
|
||||
sort_page(wspace_private_pages, sb_private_items)
|
||||
|
||||
if "System Manager" in frappe.get_roles():
|
||||
sort_page(wspace_public_pages, public_pages)
|
||||
if sb_public_items and "Workspace Manager" in frappe.get_roles():
|
||||
sort_page(wspace_public_pages, sb_public_items)
|
||||
|
||||
def sort_page(wspace_pages, pages):
|
||||
for seq, d in enumerate(pages):
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ def create_content(doc):
|
|||
del doc.charts[doc.charts.index(l)]
|
||||
if doc.shortcuts:
|
||||
invalid_links = []
|
||||
content.append({"type":"spacer","data":{"col":12,"pt":0,"pr":0,"pb":0,"pl":0}})
|
||||
if doc.charts:
|
||||
content.append({"type":"spacer","data":{"col":12,"pt":0,"pr":0,"pb":0,"pl":0}})
|
||||
content.append({"type":"header","data":{"text":doc.shortcuts_label or _("Your Shortcuts"),"level":4,"col":12,"pt":0,"pr":0,"pb":0,"pl":0}})
|
||||
for s in doc.shortcuts:
|
||||
if s.get_invalid_links()[0]:
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ frappe.views.Workspace = class Workspace {
|
|||
this.page = wrapper.page;
|
||||
this.isReadOnly = true;
|
||||
this.new_page = null;
|
||||
this.sorted_sidebar_items = [];
|
||||
this.sorted_public_items = [];
|
||||
this.sorted_private_items = [];
|
||||
this.deleted_sidebar_items = [];
|
||||
this.current_page = {};
|
||||
this.sidebar_items = {
|
||||
|
|
@ -30,8 +31,8 @@ frappe.views.Workspace = class Workspace {
|
|||
'private': {}
|
||||
};
|
||||
this.sidebar_categories = [
|
||||
"Public",
|
||||
frappe.user.first_name()
|
||||
'Public',
|
||||
frappe.user.first_name() || 'Private'
|
||||
];
|
||||
this.tools = {
|
||||
header: {
|
||||
|
|
@ -113,7 +114,7 @@ frappe.views.Workspace = class Workspace {
|
|||
}
|
||||
|
||||
get_pages() {
|
||||
return frappe.xcall("frappe.desk.doctype.workspace.workspace.get_pages");
|
||||
return frappe.xcall("frappe.desk.desktop.get_wspace_sidebar_items");
|
||||
}
|
||||
|
||||
sidebar_item_container(item) {
|
||||
|
|
@ -124,7 +125,7 @@ frappe.views.Workspace = class Workspace {
|
|||
href="/app/${item.public ? frappe.router.slug(item.title) : 'private/'+frappe.router.slug(item.title) }"
|
||||
class="item-anchor ${item.is_editable ? "" : "block-click" }" title="${item.title}"
|
||||
>
|
||||
<span>${frappe.utils.icon(item.icon || "folder-normal", "md")}</span>
|
||||
<span class="sidebar-item-icon" item-icon=${item.icon || "folder-normal"}>${frappe.utils.icon(item.icon || "folder-normal", "md")}</span>
|
||||
<span class="sidebar-item-label">${item.title}<span>
|
||||
</a>
|
||||
<div class="sidebar-item-control"></div>
|
||||
|
|
@ -146,7 +147,9 @@ frappe.views.Workspace = class Workspace {
|
|||
this.build_sidebar_section(category, root_pages);
|
||||
});
|
||||
|
||||
this.sidebar.find('.selected')[0].scrollIntoView();
|
||||
// Scroll sidebar to selected page if it is not in viewport.
|
||||
!frappe.dom.is_element_in_viewport(this.sidebar.find('.selected'))
|
||||
&& this.sidebar.find('.selected')[0].scrollIntoView();
|
||||
}
|
||||
|
||||
build_sidebar_section(title, root_pages) {
|
||||
|
|
@ -448,26 +451,36 @@ frappe.views.Workspace = class Workspace {
|
|||
animation: 150,
|
||||
fallbackOnBody: true,
|
||||
swapThreshold: 0.65,
|
||||
onEnd: function () {
|
||||
me.prepare_sorted_sidebar();
|
||||
onEnd: function (evt) {
|
||||
let is_public = $(evt.item).attr('item-public') == '1';
|
||||
me.prepare_sorted_sidebar(is_public);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
prepare_sorted_sidebar() {
|
||||
this.sorted_sidebar_items = [];
|
||||
for (let page of $('.standard-sidebar-section').find('.sidebar-item-container')) {
|
||||
prepare_sorted_sidebar(is_public) {
|
||||
if (is_public) {
|
||||
this.sorted_public_items = this.sort_sidebar(this.sidebar.find('.standard-sidebar-section').first());
|
||||
} else {
|
||||
this.sorted_private_items = this.sort_sidebar(this.sidebar.find('.standard-sidebar-section').last());
|
||||
}
|
||||
}
|
||||
|
||||
sort_sidebar($sidebar_section) {
|
||||
let sorted_items = [];
|
||||
for (let page of $sidebar_section.find('.sidebar-item-container')) {
|
||||
let parent_page = "";
|
||||
if (page.closest('.nested-container').classList.contains('sidebar-child-item')) {
|
||||
parent_page = page.parentElement.parentElement.attributes["item-name"].value;
|
||||
}
|
||||
this.sorted_sidebar_items.push({
|
||||
sorted_items.push({
|
||||
title: page.attributes['item-name'].value,
|
||||
parent_page: parent_page,
|
||||
public: page.attributes['item-public'].value
|
||||
});
|
||||
}
|
||||
return sorted_items;
|
||||
}
|
||||
|
||||
make_blocks_sortable() {
|
||||
|
|
@ -542,11 +555,16 @@ frappe.views.Workspace = class Workspace {
|
|||
this.show_sidebar_actions();
|
||||
this.make_sidebar_sortable();
|
||||
this.make_blocks_sortable();
|
||||
this.prepare_sorted_sidebar();
|
||||
this.prepare_sorted_sidebar(values.is_public);
|
||||
});
|
||||
}
|
||||
});
|
||||
d.show();
|
||||
|
||||
// to enable focusing on input field when modal is open.
|
||||
d.$wrapper.on('shown.bs.modal', function() {
|
||||
$(document).off('focusin.modal');
|
||||
});
|
||||
}
|
||||
|
||||
validate_page(values) {
|
||||
|
|
@ -652,7 +670,8 @@ frappe.views.Workspace = class Workspace {
|
|||
title: me.title,
|
||||
parent: me.parent || '',
|
||||
public: me.public || 0,
|
||||
sb_items: me.sorted_sidebar_items,
|
||||
sb_public_items: me.sorted_public_items,
|
||||
sb_private_items: me.sorted_private_items,
|
||||
deleted_pages: me.deleted_sidebar_items,
|
||||
new_widgets: new_widgets,
|
||||
blocks: JSON.stringify(outputData.blocks),
|
||||
|
|
@ -666,7 +685,8 @@ frappe.views.Workspace = class Workspace {
|
|||
me.title = '';
|
||||
me.parent = '';
|
||||
me.public = false;
|
||||
me.sorted_sidebar_items = [];
|
||||
me.sorted_public_items = [];
|
||||
me.sorted_private_items = [];
|
||||
me.deleted_sidebar_items = [];
|
||||
me.reload();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue