frappe-core-permissions について
Frappe 開発者向けの権限システムのリファレンススキルです。複数の権限レイヤー、API、フック、安全なクエリパターンを解説します。
主な機能
- 015 層の権限モデル
- 02権限チェックと共有 API
- 03レコード・フィールド単位のアクセス制御
- 04権限フックとクエリフィルタリング
- 05データマスキングのルール
- 06安全なクエリの実践
活用シーン
- 01DocType のアクセスルール設計
- 02ユーザーに見せるレコードやフィールドの制限
- 03リストクエリへの権限フィルター適用
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ impertio-studio/ frappe_claude_skill_package --skill frappe-core-permissionsインストール後、Claude Code で/frappe-core-permissionsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/anti-patterns.mdexamples.mdpermission-api-reference.mdpermission-hooks-reference.mdpermission-types-reference.md詳細
Frappe の権限
5 層からなる Frappe 権限システムの決定論的なパターンです。
権限レイヤー
| レイヤー | 制御する内容 | 設定方法 | バージョン |
|---|---|---|---|
| ロール権限 | ユーザーが実行できる操作 | DocType の権限テーブル | すべて |
| ユーザー権限 | ユーザーに表示されるレコード | User Permission DocType | すべて |
| 権限レベル | ユーザーに表示・編集されるフィールド | フィールドの permlevel プロパティ | すべて |
| 権限フック | カスタムの拒否ロジック | hooks.py | すべて |
| データマスキング | マスクされたフィールド値 | フィールドの mask プロパティ | [v16+] |
判断ツリー
Need to control access?
├── Who can Create/Read/Write/Delete a DocType? → Role Permissions
├── Which specific records can a user see? → User Permissions
├── Which fields should be hidden? → Perm Levels (permlevel 1+)
├── Which fields show masked values? → Data Masking [v16+]
├── Custom runtime deny logic? → has_permission hook
├── Filter list queries dynamically? → permission_query_conditions hook
└── Share one document with one user? → frappe.share
Checking permissions in code?
├── Before action → frappe.has_permission() or doc.has_permission()
├── Raise on denial → doc.check_permission() or throw=True
├── System bypass → doc.flags.ignore_permissions = True (ALWAYS document why)
└── List query → ALWAYS use frappe.get_list() for user-facing data
権限の種類
| 種類 | API チェック | 適用対象 |
|---|---|---|
read | frappe.has_permission(dt, "read") | すべての DocType |
write | frappe.has_permission(dt, "write") | すべての DocType |
create | frappe.has_permission(dt, "create") | すべての DocType |
delete | frappe.has_permission(dt, "delete") | すべての DocType |
submit | frappe.has_permission(dt, "submit") | 送信可能な DocType のみ |
cancel | frappe.has_permission(dt, "cancel") | 送信可能な DocType のみ |
amend | frappe.has_permission(dt, "amend") | 送信可能な DocType のみ |
select | frappe.has_permission(dt, "select") | リンクフィールド [v14+] |
report | 該当なし | レポートビルダーへのアクセス |
export | 該当なし | Excel/CSV エクスポート |
import | 該当なし | データインポートツール |
share | 該当なし | 他のユーザーとの共有 |
print | 該当なし | 印刷/PDF 生成 |
email | 該当なし | メール送信 |
mask | マスクされていない表示に対するロール権限 | データマスキング [v16+] |
自動ロール
| ロール | 割り当て先 | 備考 |
|---|---|---|
Guest | 全員(匿名ユーザーを含む) | 公開ページ |
All | 登録済みユーザー全員 | 基本的な認証済みアクセス |
Administrator | Administrator ユーザーのみ | 常にすべての権限を持ちます |
Desk User | システムユーザーのみ | [v15+] |
基本 API
権限を確認する
# DocType-level
frappe.has_permission("Sales Order", "write")
# Document-level (by name or object)
frappe.has_permission("Sales Order", "write", "SO-00001")
frappe.has_permission("Sales Order", "write", doc=doc)
# For specific user
frappe.has_permission("Sales Order", "read", user="[email protected]")
# Throw on denial
frappe.has_permission("Sales Order", "delete", throw=True)
# Debug mode — prints evaluation steps
frappe.has_permission("Sales Order", "read", debug=True)
print(frappe.local.permission_debug_log)
ドキュメントインスタンスのメソッド
doc = frappe.get_doc("Sales Order", "SO-00001")
# Returns bool
if doc.has_permission("write"):
doc.status = "Approved"
doc.save()
# Raises frappe.PermissionError if denied
doc.check_permission("write")
実効権限を取得する
from frappe.permissions import get_doc_permissions
perms = get_doc_permissions(doc)
# {'read': 1, 'write': 1, 'create': 0, 'delete': 0, ...}
perms = get_doc_permissions(doc, user="[email protected]")
ユーザー権限(レコードレベル)
ユーザーが特定のリンクフィールド値(特定の会社、地域など)だけにアクセスできるよう制限します。
from frappe.permissions import add_user_permission, remove_user_permission
# Restrict user to one company
add_user_permission(
doctype="Company",
name="My Company",
user="[email protected]",
is_default=1, # auto-fill in new documents
applicable_for="Sales Order" # only for this DocType (optional)
)
# Remove restriction
remove_user_permission("Company", "My Company", "[email protected]")
# Query current restrictions
from frappe.permissions import get_user_permissions
perms = get_user_permissions("[email protected]")
# {"Company": [{"doc": "My Company", "is_default": 1}], ...}
共有(ドキュメントレベル)
特定のユーザーに単一のドキュメントへのアクセス権を付与します。
from frappe.share import add as add_share, remove as remove_share
add_share("Sales Order", "SO-00001", "[email protected]",
read=1, write=1, share=0, notify=1)
remove_share("Sales Order", "SO-00001", "[email protected]")
# Share with everyone
add_share("Sales Order", "SO-00001", everyone=1, read=1)
フィールドレベルの権限(権限レベル)
フィールドを permlevel(0~9)でグループ化します。上位レベルを付与する前に、必ずレベル 0 を付与する必要があります。
{
"fields": [
{"fieldname": "employee_name", "permlevel": 0},
{"fieldname": "salary", "permlevel": 1}
],
"permissions": [
{"role": "Employee", "permlevel": 0, "read": 1},
{"role": "HR Manager", "permlevel": 0, "read": 1, "write": 1},
{"role": "HR Manager", "permlevel": 1, "read": 1, "write": 1}
]
}
ルール:レベルは階層を意味しません。レベル 2 がレベル 1 より「上位」というわけではありません。それぞれ独立したフィールドグループです。
データマスキング [v16+]
mask=1 のフィールドは、mask 権限のないユーザーにはマスクされた値(例:****、+91-811XXXXXXX)を表示します。
{
"fieldname": "phone_number", "fieldtype": "Data", "mask": 1
}
マスクされていない値を表示する必要があるロールには、mask 権限を付与します。
{"role": "HR Manager", "permlevel": 0, "read": 1, "mask": 1}
重大な注意:データマスキングは frappe.db.sql() や、未加工の SQL を使用するクエリレポートには適用されません。カスタム SQL クエリでは、必ず手動でマスクしてください。
権限フック
has_permission:カスタム拒否ロジック
アクセスを拒否することしかできません。許可するために True を返すことは絶対にありません。標準チェックを続行するには、常に None を返します。
# hooks.py
has_permission = {
"Sales Order": "myapp.permissions.check_order_permission"
}
# myapp/permissions.py
def check_order_permission(doc, ptype, user):
if ptype == "write" and doc.docstatus == 2:
if "Sales Manager" not in frappe.get_roles(user):
return False
return None # ALWAYS return None by default
permission_query_conditions:リストクエリをフィルタリングする
SQL の WHERE 句の一部を返します。影響するのは get_list() のみで、get_all() には影響しません。
# hooks.py
permission_query_conditions = {
"Customer": "myapp.permissions.customer_query"
}
def customer_query(user):
if not user:
user = frappe.session.user
if "Sales Manager" in frappe.get_roles(user):
return ""
return f"`tabCustomer`.owner = {frappe.db.escape(user)}"
必ず frappe.db.escape() を使用してください。未加工のユーザー入力を文字列連結に使用することは絶対に避けてください。
get_list と get_all の違い
| メソッド | ユーザー権限 | クエリフック | 用途 |
|---|---|---|---|
frappe.get_list() | 適用される | 適用される | ユーザー向けのクエリ |
frappe.get_all() | 無視される | 無視される | システム/バックグラウンドのクエリ |
ユーザーにデータを返す場合は、必ず get_list() を使用してください。get_all() はすべての権限フィルタリングを回避します。
よくあるパターン
所有者のみ編集可能
{"role": "Sales User", "read": 1, "write": 1, "create": 1, "if_owner": 1}
ロール制限付きエンドポイント
@frappe.whitelist()
def sensitive_action():
frappe.only_for(["Manager", "Administrator"])
# Only reaches here if user has one of these roles
権限を回避する(理由を必ず記載!)
# On document — ALWAYS add a comment explaining the reason
doc.flags.ignore_permissions = True
doc.save()
# On method call
doc.save(ignore_permissions=True)
doc.insert(ignore_permissions=True)
重要なルール
- 必ずアクセス制御には
frappe.has_permission()を使用してください。ロールを直接確認してはいけません - 必ずユーザー向けのクエリには
frappe.get_list()を使用してください。get_all()を使用してはいけません - 必ずクエリフック内の SQL をエスケープしてください —
frappe.db.escape(user) - 必ずクエリフックではテーブル名にプレフィックスを付けてください —
`tabDocType`.fieldname - 必ず
has_permissionフックではデフォルトでNoneを返してください。Trueを返してはいけません - 必ず権限を変更した後はキャッシュをクリアしてください —
frappe.clear_cache() - 必ず
ignore_permissionsの使用箇所にはコメントで説明を記載してください - 絶対に
has_permissionフックでエラーをスローしてはいけません。拒否するにはFalseを返してください - 絶対にpermlevel 0 を許可せずに permlevel 1 以上を許可してはいけません
- 絶対にデータマスキングがカスタム SQL クエリにも適用されると想定してはいけません[v16 以降]
アンチパターン
| してはいけないこと | 代わりに行うこと |
|---|---|
アクセス制御に if "Role" in frappe.get_roles() を使う | frappe.has_permission(dt, ptype) |
ユーザー向けのクエリに frappe.get_all() を使う | frappe.get_list() |
has_permission フックで return True を使う | return None |
SQL 内で f"owner = '{user}'" を使う | f"owner = {frappe.db.escape(user)}" |
権限フック内で frappe.throw() を使う | return False |
ユーザー向けの更新に frappe.db.set_value() を使う | 権限チェックを行ったうえで doc.save() を使う |
| エラーメッセージに機密データを含める | 汎用的な frappe.PermissionError を使う |
バージョンごとの差異
| 機能 | v14 | v15 | v16 |
|---|---|---|---|
select 権限 | あり | あり | あり |
Desk User ロール | なし | あり | あり |
データマスキング(mask フィールド) | なし | なし | あり |
mask 権限タイプ | なし | なし | あり |
| カスタム権限タイプ | なし | なし | 試験的 |
権限の優先順位
- Administrator — 必ずすべての権限を持ちます(制限できません)
- ロール権限 — 割り当てられたロールに基づきます
- ユーザー権限 — 特定のドキュメント値に制限します
- has_permission フック — 拒否のみ可能です(いずれかが
Falseなら拒否されます) - 共有 — 共有されたドキュメントへのアクセスを許可します
- if_owner — 所有するドキュメントにさらに制限します
参考ファイル
| ファイル | 内容 |
|---|---|
| permission-types-reference.md | オプションを含むすべての権限タイプ |
| permission-api-reference.md | すべてのシグネチャを含む API 全体 |
| permission-hooks-reference.md | フックのパターンと例 |
| examples.md | 動作する実装例 |
| anti-patterns.md | よくある間違いと修正方法 |
関連スキル
frappe-core-database— 権限を考慮したデータベース操作frappe-core-api— 権限チェックを備えた API エンドポイントfrappe-syntax-controllers— コントローラーでの権限検証frappe-syntax-hooks— フック設定のパターン
Frappe のドキュメントに基づき検証済み:2026-03-20 | Frappe v14/v15/v16