diff --git a/forms_pro/api/form.py b/forms_pro/api/form.py index 12348f1..80700c4 100644 --- a/forms_pro/api/form.py +++ b/forms_pro/api/form.py @@ -1,6 +1,6 @@ import frappe from frappe import _ -from frappe.share import remove +from frappe.share import add_docshare, remove from pydantic import BaseModel, Field from forms_pro.api.user import get_user @@ -128,6 +128,94 @@ def remove_form_access(form_id: str, user_email: str) -> None: return remove(doctype="Form", name=form_id, user=user_email, flags={"ignore_permissions": True}) +@frappe.whitelist() +def add_form_access( + form_id: str, + user: str, + read: bool = True, + write: bool = False, + share: bool = False, + submit: bool = False, +) -> None: + """ + Grant a user access to a form with the specified permissions. + + Uses ``ignore_share_permission`` so the record can be shared regardless of + the caller's role-level DocShare permissions — the explicit + ``frappe.has_permission`` check below enforces that only users with share + access on this particular form can invoke this endpoint. + + Args: + form_id: Name of the Form document to share. + user: Email of the user to grant access to. + read: Allow the user to read the form (default True). + write: Allow the user to edit the form (default False). + share: Allow the user to share the form with others (default False). + submit: Allow the user to submit the form (default False). + + Raises: + frappe.PermissionError: If the calling user does not have share access + on the specified form. + """ + if not frappe.has_permission("Form", "share", form_id): + frappe.throw(_("You do not have share access to this form"), frappe.PermissionError) + + add_docshare( + doctype="Form", + name=form_id, + user=user, + read=int(read), + write=int(write), + share=int(share), + submit=int(submit), + flags={"ignore_share_permission": True}, + ) + + +@frappe.whitelist() +def set_form_permission( + form_id: str, + user: str, + permission_to: str, + value: bool, +) -> None: + """ + Toggle a single permission bit for a user on a form. + + Designed for per-toggle updates from the sharing UI — only the specified + permission field is changed; all other existing permissions are preserved by + Frappe's ``add_docshare`` merge behaviour. + + Args: + form_id: Name of the Form document. + user: Email of the user whose permission is being updated. + permission_to: Which permission to update. Must be one of: + ``"read"``, ``"write"``, ``"share"``, ``"submit"``. + value: ``True`` to grant the permission, ``False`` to revoke it. + + Raises: + frappe.PermissionError: If the calling user does not have share access + on the specified form. + frappe.ValidationError: If ``permission_to`` is not a recognised + permission type. + """ + if not frappe.has_permission("Form", "share", form_id): + frappe.throw(_("You do not have share access to this form"), frappe.PermissionError) + + # Guard against arbitrary kwargs being forwarded to add_docshare + allowed_permissions = {"read", "write", "share", "submit"} + if permission_to not in allowed_permissions: + frappe.throw(_("Invalid permission type"), frappe.ValidationError) + + add_docshare( + doctype="Form", + name=form_id, + user=user, + **{permission_to: int(bool(value))}, + flags={"ignore_share_permission": True}, + ) + + @frappe.whitelist() def get_doctype_list() -> list[str]: if not frappe.has_permission("DocType", "read"): diff --git a/frontend/src/stores/form/manageForm.ts b/frontend/src/stores/form/manageForm.ts index c84b949..501ac0f 100644 --- a/frontend/src/stores/form/manageForm.ts +++ b/frontend/src/stores/form/manageForm.ts @@ -68,12 +68,11 @@ export const useManageForm = defineStore("manageForm", () => { */ function addAccess(userId: string, access: AccessPermissions) { const _access = createResource({ - url: "frappe.share.add", + url: "forms_pro.api.form.add_form_access", method: "POST", makeParams() { return { - doctype: "Form", - name: currentFormId.value, + form_id: currentFormId.value, user: userId, read: Boolean(access.read), write: Boolean(access.write), @@ -106,12 +105,11 @@ export const useManageForm = defineStore("manageForm", () => { value: boolean ) { const _permission = createResource({ - url: "frappe.share.set_permission", + url: "forms_pro.api.form.set_form_permission", method: "POST", makeParams() { return { - doctype: "Form", - name: currentFormId.value, + form_id: currentFormId.value, user: userId, permission_to: permission, value: value ? 1 : 0,