mcpField
F
frappe-errors-permissions

frappe-errors-permissions

github.com/impertio-studioセキュリティ198112 回インストール30 週間前に更新
リポジトリ

frappe-errors-permissions について

Frappe 開発者向けの権限エラー調査スキルです。ロール、ドキュメントアクセス、フック、ゲスト用エンドポイントの問題を原因と修正方法に結び付け、安全な実装ルールも示します。

主な機能

  1. 01エラーから権限原因を特定
  2. 02権限フックと検索条件の調査
  3. 03ユーザー権限とフィールド制限の確認
  4. 04共有とゲストアクセスの規則
  5. 05安全なデバッグと実装手順

活用シーン

  1. 01ドキュメントへのアクセス拒否を調査
  2. 02リスト表示から消えたレコードを調査
  3. 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.md
examples.md
patterns.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"])

重要なルール

必ず守ること
  1. 権限フックは try/except で囲む — 未処理のエラーが発生すると、すべてのアクセスが機能しなくなります
  2. has_permission では True ではなく None を返す — フックでできるのは拒否のみです
  3. クエリー条件では frappe.db.escape() を使用する — SQL インジェクションを防ぎます
  4. カスタムフックでは System Manager / Administrator を最初に確認する
  5. エンドポイントの権限チェックには frappe.has_permission(throw=True) を使用する
  6. ユーザー向けクエリーには get_all ではなく get_list を使用する — get_all は権限チェックを回避します
  7. 権限拒否を frappe.log_error() で記録し、セキュリティ監査に備える
絶対にしないこと
  1. has_permission または permission_query_conditions 内で例外をスローする — アクセスが完全に機能しなくなります
  2. has_permission で True を返す — 効果はありません。フックでできるのは制限のみです
  3. SQL に文字列フォーマットを使用する — インジェクションを防ぐため、frappe.db.escape() を使用します
  4. Guest に書き込み/削除権限を付与する — セキュリティリスクがあります
  5. 理由を記録せずに ignore_permissions を使用する — 監査上の欠落につながります
  6. 共有がロール権限に取って代わると想定する — 共有は権限を補うだけです

参考ファイル

ファイル内容
references/patterns.mdフックパターン、クエリー条件、API エンドポイントの全容
references/examples.mdhooks.py の設定を含む、完全に動作する例
references/anti-patterns.mdよくある 15 の間違いと、誤った例/正しい例の比較

関連項目

  • frappe-core-permissions — 権限システムのアーキテクチャ
  • frappe-errors-api — API のエラー処理(401/403/404)
  • frappe-errors-hooks — フックのエラー処理パターン
  • frappe-syntax-hooks — フック登録の構文

コメント