erpnext-errors-permissions

安装

npx skills add https://github.com/openaec-foundation/erpnext_anthropic_claude_development_skill_package --skill erpnext-errors-permissions
ERPNext Permissions - Error Handling
This skill covers error handling patterns for the Frappe permission system. For permission syntax, see
erpnext-permissions
. For hooks, see
erpnext-syntax-hooks
.
Version
v14/v15/v16 compatible Permission Error Handling Overview ┌─────────────────────────────────────────────────────────────────────┐ │ PERMISSION ERRORS REQUIRE SPECIAL HANDLING │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ Permission Hooks (has_permission, permission_query_conditions): │ │ ⚠️ NEVER throw errors - return False or empty string │ │ ⚠️ Errors break document access and list views │ │ ⚠️ Always provide safe fallbacks │ │ │ │ Permission Checks in Code: │ │ ✅ Use frappe.has_permission() before operations │ │ ✅ Use throw=True for automatic error handling │ │ ✅ Catch frappe.PermissionError for custom handling │ │ │ │ API Endpoints: │ │ ✅ frappe.only_for() for role-restricted endpoints │ │ ✅ doc.has_permission() before document operations │ │ ✅ Return proper HTTP 403 for access denied │ │ │ └─────────────────────────────────────────────────────────────────────┘ Main Decision: Where Is the Permission Check? ┌─────────────────────────────────────────────────────────────────────────┐ │ WHERE ARE YOU HANDLING PERMISSIONS? │ ├─────────────────────────────────────────────────────────────────────────┤ │ │ │ ► has_permission hook (hooks.py) │ │ └─► NEVER throw - return False to deny, None to defer │ │ └─► Wrap in try/except, log errors, return None on failure │ │ │ │ ► permission_query_conditions hook (hooks.py) │ │ └─► NEVER throw - return SQL condition or empty string │ │ └─► Wrap in try/except, return restrictive fallback on failure │ │ │ │ ► Whitelisted method / API endpoint │ │ └─► Use frappe.has_permission() with throw=True │ │ └─► Or catch PermissionError for custom response │ │ └─► Use frappe.only_for() for role-restricted endpoints │ │ │ │ ► Controller method │ │ └─► Use doc.has_permission() or doc.check_permission() │ │ └─► Let PermissionError propagate for standard handling │ │ │ │ ► Client Script │ │ └─► Handle in frappe.call error callback │ │ └─► Check exc type for PermissionError │ │ │ └─────────────────────────────────────────────────────────────────────────┘ Permission Hook Error Handling has_permission - NEVER Throw!

❌ WRONG - Breaks document access!

def has_permission ( doc , ptype , user ) : if doc . status == "Locked" : frappe . throw ( "Document is locked" )

DON'T DO THIS!

✅ CORRECT - Return False to deny, None to defer

def has_permission ( doc , ptype , user ) : """ Custom permission check. Returns: None: Defer to standard permission system False: Deny permission NEVER return True - hooks can only deny, not grant. """ try : user = user or frappe . session . user

Deny editing locked documents

if ptype == "write" and doc . get ( "status" ) == "Locked" : if "System Manager" not in frappe . get_roles ( user ) : return False

Deny access to confidential documents

if doc . get ( "is_confidential" ) : allowed_users = get_allowed_users ( doc . name ) if user not in allowed_users : return False

ALWAYS return None to defer to standard checks

return None except Exception : frappe . log_error ( frappe . get_traceback ( ) , f"Permission check error: { doc . name if hasattr ( doc , 'name' ) else 'unknown' } " )

Safe fallback - defer to standard system

return None permission_query_conditions - NEVER Throw!

❌ WRONG - Breaks list views!

def query_conditions ( user ) : if not user : frappe . throw ( "User required" ) return f"owner = ' { user } '"

Also SQL injection!

✅ CORRECT - Return safe SQL condition

def query_conditions ( user ) : """ Return SQL WHERE clause fragment for list filtering. Returns: str: SQL condition (empty string for no restriction) NEVER throw errors - return restrictive fallback. """ try : if not user : user = frappe . session . user roles = frappe . get_roles ( user )

Admins see all

if "System Manager" in roles : return ""

Managers see team records

if "Sales Manager" in roles : team_users = get_team_users ( user ) if team_users : escaped = ", " . join ( [ frappe . db . escape ( u ) for u in team_users ] ) return f"tabSales Order.owner IN ( { escaped } )"

Default: own records only

return f"tabSales Order.owner = { frappe . db . escape ( user ) } " except Exception : frappe . log_error ( frappe . get_traceback ( ) , f"Permission query error for { user } " )

SAFE FALLBACK: Most restrictive - own records only

return f"tabSales Order.owner = { frappe . db . escape ( frappe . session . user ) } " Permission Check Error Handling Pattern 1: Check Before Action (Recommended) @frappe . whitelist ( ) def update_order_status ( order_name , new_status ) : """Update order status with permission check."""

Check document exists

if not frappe . db . exists ( "Sales Order" , order_name ) : frappe . throw ( _ ( "Sales Order {0} not found" ) . format ( order_name ) , exc = frappe . DoesNotExistError )

Check permission - throws automatically

frappe . has_permission ( "Sales Order" , "write" , order_name , throw = True )

Now safe to proceed

frappe . db . set_value ( "Sales Order" , order_name , "status" , new_status ) return { "status" : "success" } Pattern 2: Custom Permission Error Response @frappe . whitelist ( ) def sensitive_operation ( doc_name ) : """Operation with custom permission error handling.""" try : doc = frappe . get_doc ( "Sensitive Doc" , doc_name ) doc . check_permission ( "write" ) except frappe . DoesNotExistError : frappe . throw ( _ ( "Document not found" ) , exc = frappe . DoesNotExistError ) except frappe . PermissionError :

Log attempted access

frappe . log_error ( f"Unauthorized access attempt: { doc_name } by { frappe . session . user } " , "Security Alert" )

Custom error message

frappe . throw ( _ ( "You don't have permission to perform this action. This incident has been logged." ) , exc = frappe . PermissionError )

Proceed with operation

return process_document ( doc ) Pattern 3: Role-Restricted Endpoint @frappe . whitelist ( ) def admin_dashboard_data ( ) : """Endpoint restricted to specific roles."""

This throws PermissionError if user lacks role

frappe . only_for ( [ "System Manager" , "Dashboard Admin" ] )

Only reaches here if authorized

return compile_dashboard_data ( ) @frappe . whitelist ( ) def manager_report ( ) : """Endpoint with graceful role check.""" allowed_roles = [ "Sales Manager" , "General Manager" , "System Manager" ] user_roles = frappe . get_roles ( ) if not any ( role in user_roles for role in allowed_roles ) : frappe . throw ( _ ( "This report is only available to managers" ) , exc = frappe . PermissionError ) return generate_report ( ) Error Response Patterns Standard Permission Error

Uses frappe.PermissionError - returns HTTP 403

frappe . throw ( _ ( "You don't have permission to access this resource" ) , exc = frappe . PermissionError ) Permission Error with Context def check_access ( doc ) : """Check access with helpful error message.""" if not doc . has_permission ( "read" ) : owner_name = frappe . db . get_value ( "User" , doc . owner , "full_name" ) frappe . throw ( _ ( "This document belongs to {0}. You can only view your own documents." ) . format ( owner_name ) , exc = frappe . PermissionError ) Soft Permission Denial (No Error) @frappe . whitelist ( ) def get_dashboard_widgets ( ) : """Return widgets based on user permissions.""" widgets = [ ]

Add widgets based on permissions

if frappe . has_permission ( "Sales Order" , "read" ) : widgets . append ( get_sales_widget ( ) ) if frappe . has_permission ( "Purchase Order" , "read" ) : widgets . append ( get_purchase_widget ( ) ) if frappe . has_permission ( "Employee" , "read" ) : widgets . append ( get_hr_widget ( ) )

No error if no widgets - just return empty

return widgets Client-Side Permission Error Handling JavaScript Error Handling // Handle permission errors in frappe.call frappe . call ( { method : "myapp.api.sensitive_operation" , args : { doc_name : "DOC-001" } , callback : function ( r ) { if ( r . message ) { frappe . show_alert ( { message : __ ( "Operation completed" ) , indicator : "green" } ) ; } } , error : function ( r ) { // Check if it's a permission error if ( r . exc_type === "PermissionError" ) { frappe . msgprint ( { title : __ ( "Access Denied" ) , message : __ ( "You don't have permission to perform this action." ) , indicator : "red" } ) ; } else { // Generic error handling frappe . msgprint ( { title : __ ( "Error" ) , message : r . exc || __ ( "An error occurred" ) , indicator : "red" } ) ; } } } ) ; Permission Check Before Action // Check permission before showing button frappe . ui . form . on ( "Sales Order" , { refresh : function ( frm ) { // Only show button if user has write permission if ( frm . doc . docstatus === 0 && frappe . perm . has_perm ( "Sales Order" , 0 , "write" ) ) { frm . add_custom_button ( __ ( "Special Action" ) , function ( ) { perform_special_action ( frm ) ; } ) ; } } } ) ; // Or use frappe.call to check server-side frappe . call ( { method : "frappe.client.has_permission" , args : { doctype : "Sales Order" , docname : frm . doc . name , ptype : "write" } , async : false , callback : function ( r ) { if ( r . message ) { // Has permission - show button } } } ) ; Critical Rules ✅ ALWAYS Return None in has_permission hooks - Never return True Use frappe.db.escape() in query conditions - Prevent SQL injection Wrap hooks in try/except - Errors break access entirely Log permission errors - Security audit trail Use throw=True in permission checks - Automatic error handling Provide helpful error messages - Tell users what they can do ❌ NEVER Don't throw in permission hooks - Return False instead Don't use string concatenation in SQL - SQL injection risk Don't return True in has_permission - Hooks can only deny Don't ignore permission errors - Security risk Don't expose sensitive info in errors - Security risk Quick Reference: Permission Error Handling Context Error Method Fallback has_permission hook Return False Return None permission_query_conditions Return restrictive SQL Own records filter Whitelisted method frappe.throw(exc=PermissionError) N/A Controller doc.check_permission() Let propagate Client Script error callback Show user message Reference Files File Contents references/patterns.md Complete error handling patterns references/examples.md Full working examples references/anti-patterns.md Common mistakes to avoid See Also erpnext-permissions - Permission system overview erpnext-errors-hooks - Hook error handling erpnext-errors-api - API error handling erpnext-syntax-hooks - Hook syntax

返回排行榜