frappe-errors-permissions について
Frappe 開発者向けの権限エラー調査スキルです。ロール、ドキュメントアクセス、フック、ゲスト用エンドポイントの問題を原因と修正方法に結び付け、安全な実装ルールも示します。
主な機能
- 01エラーから権限原因を特定
- 02権限フックと検索条件の調査
- 03ユーザー権限とフィールド制限の確認
- 04共有とゲストアクセスの規則
- 05安全なデバッグと実装手順
活用シーン
- 01ドキュメントへのアクセス拒否を調査
- 02リスト表示から消えたレコードを調査
- 03非表示フィールドやゲスト用 API の拒否を修正
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ impertio-studio/ frappe_claude_skill_package --skill frappe-errors-permissionsインストール後、Claude Code で/frappe-errors-permissionsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/anti-patterns.mdexamples.mdpatterns.md詳細
権限エラーの処理
権限システムの概要については frappe-core-permissions を参照してください。フックの構文については frappe-syntax-hooks を参照してください。
クイック診断:エラーメッセージ -> 原因 -> 対処方法
| エラーメッセージ | 原因 | 対処方法 |
|---|---|---|
frappe.exceptions.PermissionError | ユーザーにロールまたはドキュメントレベルのアクセス権がありません | Role Permissions Manager でロールを追加するか、User Permission を付与します |
| ドキュメントを開くと「Not permitted」と表示される | has_permission フックが False を返すか、ロールに読み取り権限がありません | frappe.permissions.get_doc_permissions(doc, user) の出力を確認します |
| リストビューにレコードが 0 件表示される | permission_query_conditions が過度に制限的な SQL を返しています | SQL 条件をデバッグし、Link フィールドの User Permissions を確認します |
| 「Not allowed to access ... for Guest」と表示される | エンドポイントに allow_guest=True がないか、DocType に Guest の読み取り権限がありません | @frappe.whitelist() に allow_guest=True を追加します |
| ロールに読み取り権限があるのにフィールドが表示されない | フィールドの perm_level が 0 より大きく、ロールにそのレベルの権限がありません | 該当する perm_level のロール権限行を追加します |
| 「User Permission restriction」によってブロックされる | Link フィールドの User Permission によりドキュメントが自動的にフィルタリングされています | そのロール行の「Apply User Permissions」のチェックを外すか、一致する User Permission を追加します |
| 共有してもアクセスできない | 共有でアクセス権は追加されますが、ロールがない状態を上書きすることはありません | ユーザーには基本となるロール権限が必須です。共有で追加されるのはドキュメントレベルの権限のみです |
ignore_permissions が効かない | get_doc ですでに権限が確認された後にフラグを設定しています | save() または insert() を呼び出す前に flags.ignore_permissions = True を設定します |
| System Manager がアクセスできない | カスタム has_permission フックがロールを確認せずに拒否しています | フックの先頭で必ず System Manager / Administrator を確認します |
判断ツリー:エラーが発生している場所は?
Permission error occurred
├── Document-level (single doc access)?
│ ├── has_permission hook returning False?
│ │ └── Debug: frappe.permissions.get_doc_permissions(doc, user)
│ ├── User Permission restricting Link field?
│ │ └── Check: frappe.get_all("User Permission", filters={"user": user})
│ ├── perm_level blocking field?
│ │ └── Check: role has permission row for that perm_level
│ └── Sharing not applying?
│ └── Check: user has base role + sharing record exists
├── List-level (0 records in list view)?
│ ├── permission_query_conditions returning bad SQL?
│ │ └── Debug: run condition manually in MariaDB console
│ ├── User Permission auto-filtering?
│ │ └── Check "Apply User Permissions" checkbox on role row
│ └── get_all vs get_list confusion?
│ └── ALWAYS use get_list for user-facing queries
├── API endpoint (403 response)?
│ ├── Missing @frappe.whitelist()?
│ │ └── Add decorator to Python method
│ ├── Missing allow_guest=True?
│ │ └── Add allow_guest parameter for public endpoints
│ └── frappe.only_for() blocking?
│ └── Check user has required role
└── System Manager bypass failing?
└── Custom hook does not check for System Manager role
権限フックのエラー
has_permission フック:例外をスローしない
# hooks.py
has_permission = {
"Sales Order": "myapp.permissions.sales_order_has_permission",
}
# WRONG — Breaks ALL document access
def sales_order_has_permission(doc, user, permission_type):
if doc.status == "Locked":
frappe.throw("Locked") # NEVER do this
# CORRECT — Return False to deny, None to defer
def sales_order_has_permission(doc, user, permission_type):
"""
ALWAYS wrap in try/except. NEVER throw. NEVER return True.
Returns: False (deny) or None (defer to standard system).
"""
try:
user = user or frappe.session.user
if user == "Administrator":
return None
# ALWAYS check System Manager early
if "System Manager" in frappe.get_roles(user):
return None
# Deny write on locked docs (but allow read)
if permission_type in ("write", "delete", "cancel"):
if doc.get("status") == "Locked":
return False
return None # Defer to standard permission system
except Exception:
frappe.log_error(frappe.get_traceback(),
f"has_permission error: {getattr(doc, 'name', 'unknown')}")
return None # Safe fallback — defer
has_permission フックの重要なルール:
- 必ず、処理を委譲する場合は
None、拒否する場合はFalseを返します。Trueは絶対に返さないでください。フックでできるのは制限のみで、権限の付与はできません。 - 関数全体を必ず
try/exceptで囲みます。処理されない例外が発生すると、その DocType へのすべてのアクセスが失敗します。 - 必ず最初に
AdministratorとSystem Managerを確認します。 - このフック内で
frappe.throw()を呼び出してはいけません。
permission_query_conditions:例外をスローしない
# hooks.py
permission_query_conditions = {
"Sales Order": "myapp.permissions.sales_order_query",
}
# WRONG — Breaks list view for all users
def sales_order_query(user):
if not user:
frappe.throw("User required") # NEVER do this
return f"owner = '{user}'" # SQL injection!
# CORRECT — Return SQL string or empty string
def sales_order_query(user):
"""
ALWAYS return a string. Empty string = no restriction.
ALWAYS use frappe.db.escape(). ALWAYS wrap in try/except.
"""
try:
user = user or frappe.session.user
if user == "Administrator":
return ""
if "System Manager" in frappe.get_roles(user):
return ""
return f"`tabSales Order`.owner = {frappe.db.escape(user)}"
except Exception:
frappe.log_error(frappe.get_traceback(), "Query conditions error")
# SAFE FALLBACK: most restrictive
return f"`tabSales Order`.owner = {frappe.db.escape(frappe.session.user)}"
permission_query_conditions の重要なルール:
- エラーは絶対にスローせず、すべてを拒否する場合は
"1=0"、または制限する SQL 文字列を返します。 - ユーザー指定の値にはすべて
frappe.db.escape()を必ず使用します。 - このフックが影響するのは
frappe.get_list()/frappe.db.get_list()のみです。frappe.get_all()/frappe.db.get_all()には影響しません。
User Permission のエラー
制限が強すぎる:レコードが表示されない
Error: User can't see any Sales Orders despite having Sales User role.
Cause: A User Permission for "Company" exists, and "Apply User Permissions"
is checked on the Sales Order role row. Sales Order has a Company
Link field, so ALL Sales Orders are filtered by that Company value.
デバッグ手順:
# Step 1: Check what User Permissions exist
frappe.get_all("User Permission",
filters={"user": "[email protected]"},
fields=["allow", "for_value", "applicable_for"])
# Step 2: Check if Apply User Permissions is checked
frappe.get_all("DocPerm",
filters={"parent": "Sales Order", "role": "Sales User"},
fields=["role", "permlevel", "apply_user_permissions"]) # [v14]
# Step 3: Check effective permissions on a specific doc
from frappe.permissions import get_doc_permissions
perms = get_doc_permissions(frappe.get_doc("Sales Order", "SO-001"), "[email protected]")
対処パターン:
- 意図しない DocType をフィルタリングする、範囲が広すぎる User Permissions を削除します。
applicable_forフィールド [v14+] を使用して、User Permission の適用対象となる DocType を制限します。- 一律のフィルタリングが不要な場合は、ロール権限行の「Apply User Permissions」のチェックを外します。
制限が弱すぎる:ユーザーにすべてが表示される
Error: User Permission set for Territory = "North" but user sees all territories.
Cause: "Apply User Permissions" is NOT checked on the role permission row,
or the DocType has no Link field for Territory.
対処方法: ロール権限行で「Apply User Permissions」にチェックが入っていることと、DocType に制限対象の DocType への Link フィールドがあることを確認します。
perm_level のエラー
Error: Field "cost_center" is invisible despite user having read permission.
Cause: Field has permlevel=1 but role only has permission for permlevel=0.
# Check which perm_levels a role has access to
frappe.get_all("DocPerm",
filters={"parent": "Sales Invoice", "role": "Accounts User"},
fields=["permlevel", "read", "write"])
対処方法: DocType の Permission テーブルに、必要な permlevel のロール用の行を追加します。
共有権限のエラー
Error: Document shared with user but user still gets PermissionError.
Cause: User has NO base role permission on the DocType. Sharing only
supplements — it never replaces role-based permissions.
# Share a document (user MUST already have a role with at least read)
frappe.share.add("Sales Order", "SO-001", "[email protected]",
read=1, write=1, share=1)
# Check if sharing grants access
frappe.share.get_sharing_permissions("Sales Order", "SO-001", "[email protected]")
ルール:
- 共有する前に、ユーザーがその DocType の読み取り権限を持つロールを少なくとも 1 つ持っていることを必ず確認します。
- 共有により、ロール権限に加えてドキュメントレベルの権限が付与されます。
- [v15+]
frappe.share.addはnotify=1を受け付け、メール通知を送信します。
Guest アクセスのエラー
Error: "Not permitted" for unauthenticated users.
Cause: DocType has no Guest read permission, or API missing allow_guest.
Web ページ / ポータルでの対処方法:
# Add Guest read permission in DocType Permission table
# Role: Guest, Level: 0, Read: checked
API エンドポイントでの対処方法:
@frappe.whitelist(allow_guest=True)
def public_endpoint():
# ALWAYS validate input — guest endpoints are exposed to the internet
pass
公開投稿用として特別に設計された DocType(例:Web Form のバックエンド)を除き、Guest に書き込み/作成/削除の権限を付与してはいけません。
デバッグワークフロー:frappe.permissions
import frappe
from frappe.permissions import get_doc_permissions
# Get all effective permissions for a user on a document
doc = frappe.get_doc("Sales Order", "SO-001")
perms = get_doc_permissions(doc, user="[email protected]")
# Returns dict: {"read": 1, "write": 0, "create": 0, ...}
# Check specific permission with full context
frappe.has_permission("Sales Order", ptype="write",
doc="SO-001", user="[email protected]", throw=False)
# List all roles for a user
frappe.get_roles("[email protected]")
# Check User Permissions
frappe.get_all("User Permission",
filters={"user": "[email protected]"},
fields=["allow", "for_value", "applicable_for", "is_default"])
重要なルール
必ず守ること
- 権限フックは try/except で囲む — 未処理のエラーが発生すると、すべてのアクセスが機能しなくなります
- has_permission では True ではなく None を返す — フックでできるのは拒否のみです
- クエリー条件では frappe.db.escape() を使用する — SQL インジェクションを防ぎます
- カスタムフックでは System Manager / Administrator を最初に確認する
- エンドポイントの権限チェックには frappe.has_permission(throw=True) を使用する
- ユーザー向けクエリーには get_all ではなく get_list を使用する — get_all は権限チェックを回避します
- 権限拒否を frappe.log_error() で記録し、セキュリティ監査に備える
絶対にしないこと
- has_permission または permission_query_conditions 内で例外をスローする — アクセスが完全に機能しなくなります
- has_permission で True を返す — 効果はありません。フックでできるのは制限のみです
- SQL に文字列フォーマットを使用する — インジェクションを防ぐため、frappe.db.escape() を使用します
- Guest に書き込み/削除権限を付与する — セキュリティリスクがあります
- 理由を記録せずに ignore_permissions を使用する — 監査上の欠落につながります
- 共有がロール権限に取って代わると想定する — 共有は権限を補うだけです
参考ファイル
| ファイル | 内容 |
|---|---|
references/patterns.md | フックパターン、クエリー条件、API エンドポイントの全容 |
references/examples.md | hooks.py の設定を含む、完全に動作する例 |
references/anti-patterns.md | よくある 15 の間違いと、誤った例/正しい例の比較 |
関連項目
frappe-core-permissions— 権限システムのアーキテクチャfrappe-errors-api— API のエラー処理(401/403/404)frappe-errors-hooks— フックのエラー処理パターンfrappe-syntax-hooks— フック登録の構文
コメント
Markdown 対応