IDK why we truly need this, except maybe debugging sometimes. This just causes confusion and people keep reporting this as security issue.
835 lines
24 KiB
Python
835 lines
24 KiB
Python
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
|
|
# License: MIT. See LICENSE
|
|
import copy
|
|
import functools
|
|
|
|
import frappe
|
|
import frappe.share
|
|
from frappe import _, msgprint
|
|
from frappe.query_builder import DocType
|
|
from frappe.utils import cint, cstr
|
|
|
|
rights = (
|
|
"select",
|
|
"read",
|
|
"write",
|
|
"create",
|
|
"delete",
|
|
"submit",
|
|
"cancel",
|
|
"amend",
|
|
"print",
|
|
"email",
|
|
"report",
|
|
"import",
|
|
"export",
|
|
"share",
|
|
)
|
|
|
|
|
|
GUEST_ROLE = "Guest"
|
|
ALL_USER_ROLE = "All" # This includes website users too.
|
|
SYSTEM_USER_ROLE = "Desk User"
|
|
ADMIN_ROLE = "Administrator"
|
|
|
|
|
|
# These roles are automatically assigned based on user type
|
|
AUTOMATIC_ROLES = (GUEST_ROLE, ALL_USER_ROLE, SYSTEM_USER_ROLE, ADMIN_ROLE)
|
|
|
|
|
|
def print_has_permission_check_logs(func):
|
|
@functools.wraps(func)
|
|
def inner(*args, **kwargs):
|
|
print_logs = kwargs.get("print_logs", True)
|
|
self_perm_check = True if not kwargs.get("user") else kwargs.get("user") == frappe.session.user
|
|
|
|
if print_logs:
|
|
frappe.flags["has_permission_check_logs"] = []
|
|
|
|
result = func(*args, **kwargs)
|
|
|
|
# print only if access denied
|
|
# and if user is checking his own permission
|
|
if not result and self_perm_check and print_logs:
|
|
msgprint(("<br>").join(frappe.flags.get("has_permission_check_logs", [])))
|
|
|
|
if print_logs:
|
|
frappe.flags.pop("has_permission_check_logs", None)
|
|
return result
|
|
|
|
return inner
|
|
|
|
|
|
def _debug_log(log: str):
|
|
if not hasattr(frappe.local, "permission_debug_log"):
|
|
frappe.local.permission_debug_log = []
|
|
frappe.local.permission_debug_log.append(log)
|
|
|
|
|
|
def _pop_debug_log() -> list[str]:
|
|
if log := getattr(frappe.local, "permission_debug_log", None):
|
|
del frappe.local.permission_debug_log
|
|
return log
|
|
return []
|
|
|
|
|
|
@print_has_permission_check_logs
|
|
def has_permission(
|
|
doctype,
|
|
ptype="read",
|
|
doc=None,
|
|
user=None,
|
|
*,
|
|
parent_doctype=None,
|
|
print_logs=True,
|
|
debug=False,
|
|
) -> bool:
|
|
"""Return True if user has permission `ptype` for given `doctype`.
|
|
If `doc` is passed, also check user, share and owner permissions.
|
|
|
|
:param doctype: DocType to check permission for
|
|
:param ptype: Permission Type to check
|
|
:param doc: Check User Permissions for specified document.
|
|
:param user: User to check permission for. Defaults to current user.
|
|
:param print_logs: If True, will display a message using frappe.msgprint
|
|
which explains why the permission check failed.
|
|
:param parent_doctype:
|
|
Required when checking permission for a child DocType (unless doc is specified)
|
|
"""
|
|
|
|
if not user:
|
|
user = frappe.session.user
|
|
|
|
if user == "Administrator":
|
|
debug and _debug_log("Allowed everything because user is Administrator")
|
|
return True
|
|
|
|
if ptype == "share" and frappe.get_system_settings("disable_document_sharing"):
|
|
debug and _debug_log("User can't share because sharing is disabled globally from system settings")
|
|
return False
|
|
|
|
if not doc and hasattr(doctype, "doctype"):
|
|
# first argument can be doc or doctype
|
|
doc = doctype
|
|
doctype = doc.doctype
|
|
|
|
if frappe.is_table(doctype):
|
|
return has_child_permission(
|
|
doctype,
|
|
ptype,
|
|
doc,
|
|
user,
|
|
parent_doctype,
|
|
debug=debug,
|
|
print_logs=print_logs,
|
|
)
|
|
|
|
meta = frappe.get_meta(doctype)
|
|
|
|
if doc:
|
|
if isinstance(doc, str | int):
|
|
doc = frappe.get_doc(meta.name, doc)
|
|
perm = get_doc_permissions(doc, user=user, ptype=ptype, debug=debug).get(ptype)
|
|
if not perm:
|
|
debug and _debug_log(
|
|
"Permission check failed from role permission system. Check if user's role grant them permission to the document."
|
|
)
|
|
msg = _("User {0} does not have access to this document").format(frappe.bold(user))
|
|
if frappe.has_permission(doc.doctype):
|
|
msg += f": {_(doc.doctype)} - {doc.name}"
|
|
push_perm_check_log(msg, debug=debug)
|
|
else:
|
|
if ptype == "submit" and not cint(meta.is_submittable):
|
|
push_perm_check_log(_("Document Type is not submittable"), debug=debug)
|
|
return False
|
|
|
|
if ptype == "import" and not cint(meta.allow_import):
|
|
push_perm_check_log(_("Document Type is not importable"), debug=debug)
|
|
return False
|
|
|
|
role_permissions = get_role_permissions(meta, user=user, debug=debug)
|
|
debug and _debug_log(
|
|
"User has following permissions using role permission system: "
|
|
+ frappe.as_json(role_permissions, indent=8)
|
|
)
|
|
|
|
perm = role_permissions.get(ptype)
|
|
|
|
if not perm:
|
|
push_perm_check_log(
|
|
_("User {0} does not have doctype access via role permission for document {1}").format(
|
|
frappe.bold(user), frappe.bold(_(doctype))
|
|
),
|
|
debug=debug,
|
|
)
|
|
|
|
def false_if_not_shared():
|
|
if ptype not in ("read", "write", "share", "submit", "email", "print"):
|
|
debug and _debug_log(f"Permission type {ptype} can not be shared")
|
|
return False
|
|
|
|
rights = ["read" if ptype in ("email", "print") else ptype]
|
|
|
|
if doc:
|
|
doc_name = get_doc_name(doc)
|
|
shared = frappe.share.get_shared(
|
|
doctype,
|
|
user,
|
|
rights=rights,
|
|
filters=[["share_name", "=", doc_name]],
|
|
limit=1,
|
|
)
|
|
debug and _debug_log(f"Document is shared with user for {ptype}? {bool(shared)}")
|
|
return bool(shared)
|
|
|
|
elif frappe.share.get_shared(doctype, user, rights=rights, limit=1):
|
|
# if atleast one shared doc of that type, then return True
|
|
# this is used in db_query to check if permission on DocType
|
|
debug and _debug_log(f"At least one document is shared with user with perm: {rights}")
|
|
return True
|
|
|
|
return False
|
|
|
|
if not perm:
|
|
debug and _debug_log("Checking if document/doctype is explicitly shared with user")
|
|
perm = false_if_not_shared()
|
|
|
|
return bool(perm)
|
|
|
|
|
|
def get_doc_permissions(doc, user=None, ptype=None, debug=False):
|
|
"""Return a dict of evaluated permissions for given `doc` like `{"read":1, "write":1}`"""
|
|
if not user:
|
|
user = frappe.session.user
|
|
|
|
meta = frappe.get_meta(doc.doctype)
|
|
|
|
def is_user_owner():
|
|
return (doc.get("owner") or "").lower() == user.lower()
|
|
|
|
if not has_controller_permissions(doc, ptype, user=user, debug=debug):
|
|
push_perm_check_log(_("Not allowed via controller permission check"), debug=debug)
|
|
return {ptype: 0}
|
|
|
|
permissions = copy.deepcopy(get_role_permissions(meta, user=user, is_owner=is_user_owner(), debug=debug))
|
|
|
|
debug and _debug_log(
|
|
"User has following permissions using role permission system: "
|
|
+ frappe.as_json(permissions, indent=8)
|
|
)
|
|
|
|
if not cint(meta.is_submittable):
|
|
permissions["submit"] = 0
|
|
|
|
if not cint(meta.allow_import):
|
|
permissions["import"] = 0
|
|
|
|
# Override with `if_owner` perms irrespective of user
|
|
if permissions.get("has_if_owner_enabled"):
|
|
# apply owner permissions on top of existing permissions
|
|
# some access might be only for the owner
|
|
# eg. everyone might have read access but only owner can delete
|
|
permissions.update(permissions.get("if_owner", {}))
|
|
debug and _debug_log(
|
|
"User is owner of document, so permissions are updated to: " + frappe.as_json(permissions)
|
|
)
|
|
|
|
if not has_user_permission(doc, user, debug=debug):
|
|
if is_user_owner():
|
|
# replace with owner permissions
|
|
permissions = permissions.get("if_owner", {})
|
|
# if_owner does not come with create rights...
|
|
permissions["create"] = 0
|
|
debug and _debug_log("User has only 'If owner' permissions because of User Permissions")
|
|
else:
|
|
debug and _debug_log("User has no permissions because of User Permissions")
|
|
permissions = {}
|
|
|
|
debug and _debug_log(
|
|
"Final applicable permissions after evaluating user permissions: "
|
|
+ frappe.as_json(permissions, indent=8)
|
|
)
|
|
return permissions
|
|
|
|
|
|
def get_role_permissions(doctype_meta, user=None, is_owner=None, debug=False):
|
|
"""
|
|
Return dict of evaluated role permissions like:
|
|
{
|
|
"read": 1,
|
|
"write": 0,
|
|
// if "if_owner" is enabled
|
|
"if_owner":
|
|
{
|
|
"read": 1,
|
|
"write": 0
|
|
}
|
|
}
|
|
"""
|
|
if isinstance(doctype_meta, str):
|
|
doctype_meta = frappe.get_meta(doctype_meta) # assuming doctype name was passed
|
|
|
|
if not user:
|
|
user = frappe.session.user
|
|
|
|
cache_key = (doctype_meta.name, user, bool(is_owner))
|
|
|
|
if user == "Administrator":
|
|
debug and _debug_log("all permissions granted because user is Administrator")
|
|
return allow_everything()
|
|
|
|
if not frappe.local.role_permissions.get(cache_key) or debug:
|
|
perms = frappe._dict(if_owner={})
|
|
|
|
roles = frappe.get_roles(user)
|
|
debug and _debug_log("User has following roles: " + str(roles))
|
|
|
|
def is_perm_applicable(perm):
|
|
return perm.role in roles and cint(perm.permlevel) == 0
|
|
|
|
def has_permission_without_if_owner_enabled(ptype):
|
|
return any(p.get(ptype, 0) and not p.get("if_owner", 0) for p in applicable_permissions)
|
|
|
|
applicable_permissions = list(filter(is_perm_applicable, getattr(doctype_meta, "permissions", [])))
|
|
has_if_owner_enabled = any(p.get("if_owner", 0) for p in applicable_permissions)
|
|
perms["has_if_owner_enabled"] = has_if_owner_enabled
|
|
|
|
for ptype in rights:
|
|
pvalue = any(p.get(ptype, 0) for p in applicable_permissions)
|
|
# check if any perm object allows perm type
|
|
perms[ptype] = cint(pvalue)
|
|
if (
|
|
pvalue
|
|
and has_if_owner_enabled
|
|
and not has_permission_without_if_owner_enabled(ptype)
|
|
and ptype != "create"
|
|
):
|
|
perms["if_owner"][ptype] = cint(pvalue and is_owner)
|
|
# has no access if not owner
|
|
# only provide select or read access so that user is able to at-least access list
|
|
# (and the documents will be filtered based on owner sin further checks)
|
|
perms[ptype] = 1 if ptype in ("select", "read") else 0
|
|
|
|
frappe.local.role_permissions[cache_key] = perms
|
|
|
|
return frappe.local.role_permissions[cache_key]
|
|
|
|
|
|
def get_user_permissions(user):
|
|
from frappe.core.doctype.user_permission.user_permission import get_user_permissions
|
|
|
|
return get_user_permissions(user)
|
|
|
|
|
|
def has_user_permission(doc, user=None, debug=False):
|
|
"""Return True if User is allowed to view considering User Permissions."""
|
|
from frappe.core.doctype.user_permission.user_permission import get_user_permissions
|
|
|
|
user_permissions = get_user_permissions(user)
|
|
|
|
if not user_permissions:
|
|
# no user permission rules specified for this doctype
|
|
debug and _debug_log("User is not affected by any user permissions")
|
|
return True
|
|
|
|
# don't apply strict user permissions for single doctypes since they contain empty link fields
|
|
apply_strict_user_permissions = (
|
|
False if doc.meta.issingle else frappe.get_system_settings("apply_strict_user_permissions")
|
|
)
|
|
if apply_strict_user_permissions:
|
|
debug and _debug_log("Strict user permissions will be applied")
|
|
|
|
doctype = doc.get("doctype")
|
|
docname = doc.get("name")
|
|
|
|
# STEP 1: ---------------------
|
|
# check user permissions on self
|
|
if doctype in user_permissions:
|
|
allowed_docs = get_allowed_docs_for_doctype(user_permissions.get(doctype, []), doctype)
|
|
|
|
# if allowed_docs is empty it states that there is no applicable permission under the current doctype
|
|
|
|
# only check if allowed_docs is not empty
|
|
if allowed_docs and str(docname) not in allowed_docs:
|
|
# no user permissions for this doc specified
|
|
debug and _debug_log(
|
|
"User doesn't have access to this document because of User Permissions, allowed documents: "
|
|
+ str(allowed_docs)
|
|
)
|
|
push_perm_check_log(_("Not allowed for {0}: {1}").format(_(doctype), docname), debug=debug)
|
|
return False
|
|
else:
|
|
debug and _debug_log(f"User Has access to {docname} via User Permissions.")
|
|
|
|
# STEP 2: ---------------------------------
|
|
# check user permissions in all link fields
|
|
|
|
def check_user_permission_on_link_fields(d):
|
|
# check user permissions for all the link fields of the given
|
|
# document object d
|
|
#
|
|
# called for both parent and child records
|
|
|
|
meta = frappe.get_meta(d.get("doctype"))
|
|
|
|
# check all link fields for user permissions
|
|
for field in meta.get_link_fields():
|
|
if field.ignore_user_permissions:
|
|
continue
|
|
|
|
# empty value, do you still want to apply user permissions?
|
|
if not d.get(field.fieldname) and not apply_strict_user_permissions:
|
|
# nah, not strict
|
|
continue
|
|
|
|
if field.options not in user_permissions:
|
|
continue
|
|
|
|
# get the list of all allowed values for this link
|
|
allowed_docs = get_allowed_docs_for_doctype(user_permissions.get(field.options, []), doctype)
|
|
|
|
if allowed_docs and d.get(field.fieldname) not in allowed_docs:
|
|
# restricted for this link field, and no matching values found
|
|
# make the right message and exit
|
|
if d.get("parentfield"):
|
|
# "You are not allowed to access this Employee record because it is linked
|
|
# to Company 'Restricted Company' in row 3, field Reference Type"
|
|
msg = _(
|
|
"You are not allowed to access this {0} record because it is linked to {1} '{2}' in row {3}, field {4}"
|
|
).format(
|
|
_(meta.doctype),
|
|
_(field.options),
|
|
d.get(field.fieldname) or _("empty"),
|
|
d.idx,
|
|
_(field.label, context=field.parent) if field.label else field.fieldname,
|
|
)
|
|
else:
|
|
# "You are not allowed to access Company 'Restricted Company' in field Reference Type"
|
|
msg = _(
|
|
"You are not allowed to access this {0} record because it is linked to {1} '{2}' in field {3}"
|
|
).format(
|
|
_(meta.doctype),
|
|
_(field.options),
|
|
d.get(field.fieldname) or _("empty"),
|
|
_(field.label, context=field.parent) if field.label else field.fieldname,
|
|
)
|
|
|
|
push_perm_check_log(msg, debug=debug)
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
if not check_user_permission_on_link_fields(doc):
|
|
return False
|
|
|
|
for d in doc.get_all_children():
|
|
if not check_user_permission_on_link_fields(d):
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def has_controller_permissions(doc, ptype, user=None, debug=False) -> bool:
|
|
"""Return controller permissions if denied, True if not defined.
|
|
|
|
Controllers can only deny permission, they can not explicitly grant any permission that wasn't
|
|
already present."""
|
|
if not user:
|
|
user = frappe.session.user
|
|
|
|
methods = frappe.get_hooks("has_permission").get(doc.doctype, [])
|
|
|
|
if not methods:
|
|
return True
|
|
|
|
for method in reversed(methods):
|
|
controller_permission = frappe.call(method, doc=doc, ptype=ptype, user=user, debug=debug)
|
|
debug and _debug_log(f"Controller permission check from {method}: {controller_permission}")
|
|
if not controller_permission:
|
|
return bool(controller_permission)
|
|
|
|
return True
|
|
|
|
|
|
def get_doctypes_with_read():
|
|
return list({cstr(p.parent) for p in get_valid_perms() if p.parent})
|
|
|
|
|
|
def get_valid_perms(doctype=None, user=None):
|
|
"""Get valid permissions for the current user from DocPerm and Custom DocPerm"""
|
|
roles = get_roles(user)
|
|
|
|
perms = get_perms_for(roles)
|
|
custom_perms = get_perms_for(roles, "Custom DocPerm")
|
|
|
|
doctypes_with_custom_perms = get_doctypes_with_custom_docperms()
|
|
for p in perms:
|
|
if p.parent not in doctypes_with_custom_perms:
|
|
custom_perms.append(p)
|
|
|
|
if doctype:
|
|
return [p for p in custom_perms if p.parent == doctype]
|
|
else:
|
|
return custom_perms
|
|
|
|
|
|
def get_all_perms(role):
|
|
"""Return valid permissions for a given role."""
|
|
perms = frappe.get_all("DocPerm", fields="*", filters=dict(role=role))
|
|
custom_perms = frappe.get_all("Custom DocPerm", fields="*", filters=dict(role=role))
|
|
doctypes_with_custom_perms = frappe.get_all("Custom DocPerm", pluck="parent", distinct=True)
|
|
|
|
for p in perms:
|
|
if p.parent not in doctypes_with_custom_perms:
|
|
custom_perms.append(p)
|
|
return custom_perms
|
|
|
|
|
|
def get_roles(user=None, with_standard=True):
|
|
"""get roles of current user"""
|
|
if not user:
|
|
user = frappe.session.user
|
|
|
|
if user == "Guest" or not user:
|
|
return [GUEST_ROLE]
|
|
|
|
def get():
|
|
if user == "Administrator":
|
|
return frappe.get_all("Role", pluck="name") # return all available roles
|
|
else:
|
|
table = DocType("Has Role")
|
|
roles = (
|
|
frappe.qb.from_(table)
|
|
.where(
|
|
(table.parenttype == "User")
|
|
& (table.parent == user)
|
|
& (table.role.notin(AUTOMATIC_ROLES))
|
|
)
|
|
.select(table.role)
|
|
.run(pluck=True)
|
|
)
|
|
roles += [ALL_USER_ROLE, GUEST_ROLE]
|
|
if is_system_user(user):
|
|
roles.append(SYSTEM_USER_ROLE)
|
|
return roles
|
|
|
|
roles = frappe.cache.hget("roles", user, get)
|
|
|
|
# filter standard if required
|
|
if not with_standard:
|
|
roles = [r for r in roles if r not in AUTOMATIC_ROLES]
|
|
|
|
return roles
|
|
|
|
|
|
def get_doctype_roles(doctype, access_type="read"):
|
|
"""Return a list of roles that are allowed to access the given `doctype`."""
|
|
meta = frappe.get_meta(doctype)
|
|
return [d.role for d in meta.get("permissions") if d.get(access_type)]
|
|
|
|
|
|
def get_perms_for(roles, perm_doctype="DocPerm"):
|
|
"""Get perms for given roles"""
|
|
filters = {"permlevel": 0, "docstatus": 0, "role": ["in", roles]}
|
|
return frappe.get_all(perm_doctype, fields=["*"], filters=filters)
|
|
|
|
|
|
def get_doctypes_with_custom_docperms():
|
|
"""Return all the doctypes with Custom Docperms."""
|
|
|
|
doctypes = frappe.get_all("Custom DocPerm", fields=["parent"], distinct=1)
|
|
return [d.parent for d in doctypes]
|
|
|
|
|
|
def add_user_permission(
|
|
doctype,
|
|
name,
|
|
user,
|
|
ignore_permissions=False,
|
|
applicable_for=None,
|
|
is_default=0,
|
|
hide_descendants=0,
|
|
):
|
|
"""Add user permission"""
|
|
from frappe.core.doctype.user_permission.user_permission import user_permission_exists
|
|
|
|
if not user_permission_exists(user, doctype, name, applicable_for):
|
|
if not frappe.db.exists(doctype, name):
|
|
frappe.throw(_("{0} {1} not found").format(_(doctype), name), frappe.DoesNotExistError)
|
|
|
|
frappe.get_doc(
|
|
doctype="User Permission",
|
|
user=user,
|
|
allow=doctype,
|
|
for_value=name,
|
|
is_default=is_default,
|
|
applicable_for=applicable_for,
|
|
apply_to_all_doctypes=0 if applicable_for else 1,
|
|
hide_descendants=hide_descendants,
|
|
).insert(ignore_permissions=ignore_permissions)
|
|
|
|
|
|
def remove_user_permission(doctype, name, user):
|
|
user_permission_name = frappe.db.get_value(
|
|
"User Permission", dict(user=user, allow=doctype, for_value=name)
|
|
)
|
|
frappe.delete_doc("User Permission", user_permission_name)
|
|
|
|
|
|
def clear_user_permissions_for_doctype(doctype, user=None):
|
|
filters = {"allow": doctype}
|
|
if user:
|
|
filters["user"] = user
|
|
user_permissions_for_doctype = frappe.get_all("User Permission", filters=filters)
|
|
for d in user_permissions_for_doctype:
|
|
frappe.delete_doc("User Permission", d.name)
|
|
|
|
|
|
def can_import(doctype, raise_exception=False):
|
|
if not ("System Manager" in frappe.get_roles() or has_permission(doctype, "import")):
|
|
if raise_exception:
|
|
raise frappe.PermissionError(f"You are not allowed to import: {doctype}")
|
|
else:
|
|
return False
|
|
return True
|
|
|
|
|
|
def can_export(doctype, raise_exception=False):
|
|
if "System Manager" in frappe.get_roles():
|
|
return True
|
|
else:
|
|
role_permissions = frappe.permissions.get_role_permissions(doctype)
|
|
has_access = role_permissions.get("export") or role_permissions.get("if_owner").get("export")
|
|
if not has_access and raise_exception:
|
|
raise frappe.PermissionError(_("You are not allowed to export {} doctype").format(doctype))
|
|
return has_access
|
|
|
|
|
|
def update_permission_property(
|
|
doctype,
|
|
role,
|
|
permlevel,
|
|
ptype,
|
|
value=None,
|
|
validate=True,
|
|
if_owner=0,
|
|
):
|
|
"""Update a property in Custom Perm"""
|
|
from frappe.core.doctype.doctype.doctype import validate_permissions_for_doctype
|
|
|
|
out = setup_custom_perms(doctype)
|
|
|
|
name = frappe.db.get_value(
|
|
"Custom DocPerm", dict(parent=doctype, role=role, permlevel=permlevel, if_owner=if_owner)
|
|
)
|
|
table = DocType("Custom DocPerm")
|
|
frappe.qb.update(table).set(ptype, value).where(table.name == name).run()
|
|
|
|
if validate:
|
|
validate_permissions_for_doctype(doctype)
|
|
|
|
return out
|
|
|
|
|
|
def setup_custom_perms(parent):
|
|
"""if custom permssions are not setup for the current doctype, set them up"""
|
|
if not frappe.db.exists("Custom DocPerm", dict(parent=parent)):
|
|
copy_perms(parent)
|
|
return True
|
|
|
|
|
|
def add_permission(doctype, role, permlevel=0, ptype=None):
|
|
"""Add a new permission rule to the given doctype
|
|
for the given Role and Permission Level"""
|
|
from frappe.core.doctype.doctype.doctype import validate_permissions_for_doctype
|
|
|
|
setup_custom_perms(doctype)
|
|
|
|
if frappe.db.get_value(
|
|
"Custom DocPerm", dict(parent=doctype, role=role, permlevel=permlevel, if_owner=0)
|
|
):
|
|
frappe.msgprint(
|
|
_("Rule for this doctype, role, permlevel and if-owner combination already exists.").format(
|
|
doctype,
|
|
),
|
|
alert=True,
|
|
)
|
|
return
|
|
|
|
if not ptype:
|
|
ptype = "read"
|
|
|
|
custom_docperm = frappe.get_doc(
|
|
{
|
|
"doctype": "Custom DocPerm",
|
|
"__islocal": 1,
|
|
"parent": doctype,
|
|
"parenttype": "DocType",
|
|
"parentfield": "permissions",
|
|
"role": role,
|
|
"permlevel": permlevel,
|
|
ptype: 1,
|
|
}
|
|
)
|
|
|
|
custom_docperm.save()
|
|
|
|
validate_permissions_for_doctype(doctype)
|
|
return custom_docperm.name
|
|
|
|
|
|
def copy_perms(parent):
|
|
"""Copy all DocPerm in to Custom DocPerm for the given document"""
|
|
for d in frappe.get_all("DocPerm", fields="*", filters=dict(parent=parent)):
|
|
custom_perm = frappe.new_doc("Custom DocPerm")
|
|
custom_perm.update(d)
|
|
custom_perm.insert(ignore_permissions=True)
|
|
|
|
|
|
def reset_perms(doctype):
|
|
"""Reset permissions for given doctype."""
|
|
from frappe.desk.notifications import delete_notification_count_for
|
|
|
|
delete_notification_count_for(doctype)
|
|
frappe.db.delete("Custom DocPerm", {"parent": doctype})
|
|
|
|
|
|
def get_linked_doctypes(dt: str) -> list:
|
|
meta = frappe.get_meta(dt)
|
|
linked_doctypes = [dt] + [
|
|
d.options
|
|
for d in meta.get(
|
|
"fields",
|
|
{"fieldtype": "Link", "ignore_user_permissions": ("!=", 1), "options": ("!=", "[Select]")},
|
|
)
|
|
]
|
|
|
|
return list(set(linked_doctypes))
|
|
|
|
|
|
def get_doc_name(doc):
|
|
if not doc:
|
|
return None
|
|
return doc if isinstance(doc, str) else str(doc.name)
|
|
|
|
|
|
def allow_everything():
|
|
"""Return a dict with access to everything, eg. {"read": 1, "write": 1, ...}."""
|
|
return {ptype: 1 for ptype in rights}
|
|
|
|
|
|
def get_allowed_docs_for_doctype(user_permissions, doctype):
|
|
"""Return all the docs from the passed `user_permissions` that are allowed under provided doctype."""
|
|
return filter_allowed_docs_for_doctype(user_permissions, doctype, with_default_doc=False)
|
|
|
|
|
|
def filter_allowed_docs_for_doctype(user_permissions, doctype, with_default_doc=True):
|
|
"""Return all the docs from the passed `user_permissions` that are
|
|
allowed under provided doctype along with default doc value if `with_default_doc` is set."""
|
|
allowed_doc = []
|
|
default_doc = None
|
|
for doc in user_permissions:
|
|
if not doc.get("applicable_for") or doc.get("applicable_for") == doctype:
|
|
allowed_doc.append(doc.get("doc"))
|
|
if doc.get("is_default") or len(user_permissions) == 1 and with_default_doc:
|
|
default_doc = doc.get("doc")
|
|
|
|
return (allowed_doc, default_doc) if with_default_doc else allowed_doc
|
|
|
|
|
|
def push_perm_check_log(log, debug=False):
|
|
debug and _debug_log(log)
|
|
if frappe.flags.get("has_permission_check_logs") is None:
|
|
return
|
|
|
|
frappe.flags.get("has_permission_check_logs").append(log)
|
|
|
|
|
|
def has_child_permission(
|
|
child_doctype,
|
|
ptype="read",
|
|
child_doc=None,
|
|
user=None,
|
|
parent_doctype=None,
|
|
*,
|
|
debug=False,
|
|
print_logs=True,
|
|
) -> bool:
|
|
debug and _debug_log("This doctype is a child table, permissions will be checked on parent.")
|
|
if isinstance(child_doc, str):
|
|
child_doc = frappe.db.get_value(
|
|
child_doctype,
|
|
child_doc,
|
|
("parent", "parenttype", "parentfield"),
|
|
as_dict=True,
|
|
)
|
|
|
|
if child_doc:
|
|
parent_doctype = child_doc.parenttype
|
|
|
|
if not parent_doctype:
|
|
push_perm_check_log(
|
|
_("Please specify a valid parent DocType for {0}").format(frappe.bold(child_doctype)),
|
|
debug=debug,
|
|
)
|
|
return False
|
|
|
|
parent_meta = frappe.get_meta(parent_doctype)
|
|
|
|
if parent_meta.istable or not (
|
|
valid_parentfields := [
|
|
df.fieldname for df in parent_meta.get_table_fields() if df.options == child_doctype
|
|
]
|
|
):
|
|
push_perm_check_log(
|
|
_("{0} is not a valid parent DocType for {1}").format(
|
|
frappe.bold(parent_doctype), frappe.bold(child_doctype)
|
|
),
|
|
debug=debug,
|
|
)
|
|
return False
|
|
|
|
if child_doc:
|
|
parentfield = child_doc.parentfield
|
|
if not parentfield:
|
|
push_perm_check_log(
|
|
_("Parentfield not specified in {0}: {1}").format(
|
|
frappe.bold(child_doctype), frappe.bold(child_doc.name)
|
|
),
|
|
debug=debug,
|
|
)
|
|
return False
|
|
|
|
if parentfield not in valid_parentfields:
|
|
push_perm_check_log(
|
|
_("{0} is not a valid parentfield for {1}").format(
|
|
frappe.bold(parentfield), frappe.bold(child_doctype)
|
|
),
|
|
debug=debug,
|
|
)
|
|
return False
|
|
|
|
permlevel = parent_meta.get_field(parentfield).permlevel
|
|
accessible_permlevels = parent_meta.get_permlevel_access(ptype, user=user)
|
|
if permlevel > 0 and permlevel not in accessible_permlevels:
|
|
push_perm_check_log(
|
|
_("Insufficient Permission Level for {0}").format(frappe.bold(parent_doctype)), debug=debug
|
|
)
|
|
debug and _debug_log(
|
|
f"This table is perm level {permlevel} but user only has access to {accessible_permlevels}"
|
|
)
|
|
return False
|
|
|
|
return has_permission(
|
|
parent_doctype,
|
|
ptype=ptype,
|
|
doc=child_doc and getattr(child_doc, "parent_doc", child_doc.parent),
|
|
user=user,
|
|
print_logs=print_logs,
|
|
debug=debug,
|
|
)
|
|
|
|
|
|
def is_system_user(user: str | None = None) -> bool:
|
|
return frappe.get_cached_value("User", user or frappe.session.user, "user_type") == "System User"
|