$npx -y skills add Lonsdale201/wp-agent-skills --skill br-resource-policyConfigure better-route 1.1 Resource action and field authorization. Use for ResourcePolicy::publicReadPrivateWrite, adminOnly, capabilities, callbacks, Resource::policy, permissionCallback, per-action rules, wildcard rules, fieldPolicy, public Resource OpenAPI security, ownership
| 1 | # better-route: Resource authorization |
| 2 | |
| 3 | Use a Resource policy to decide who may call each generated action. Use `fieldPolicy` separately to authorize individual incoming fields. |
| 4 | |
| 5 | ## Presets |
| 6 | |
| 7 | ```php |
| 8 | use BetterRoute\Resource\ResourcePolicy; |
| 9 | |
| 10 | ->policy(ResourcePolicy::publicReadPrivateWrite('edit_posts')) |
| 11 | ->policy(ResourcePolicy::adminOnly('manage_options')) |
| 12 | ->policy(ResourcePolicy::capabilities([ |
| 13 | 'list' => 'read_private_reports', |
| 14 | 'get' => 'read_private_reports', |
| 15 | 'create' => ['edit_posts', 'manage_woocommerce'], // any-of |
| 16 | 'update' => 'edit_posts', |
| 17 | 'delete' => 'delete_posts', |
| 18 | ])) |
| 19 | ->policy(ResourcePolicy::callbacks([ |
| 20 | 'update' => static fn ($request, string $action): bool => |
| 21 | current_user_can('edit_post', (int) $request->get_param('id')), |
| 22 | ])) |
| 23 | ``` |
| 24 | |
| 25 | Supported action keys are `list`, `get`, `create`, `update`, `delete`, and fallback `*`. `update` covers PUT and PATCH. |
| 26 | |
| 27 | Rule values: |
| 28 | |
| 29 | - boolean: allow/deny; |
| 30 | - non-empty capability string: `current_user_can($capability)`; |
| 31 | - capability list: any capability may pass; |
| 32 | - callable: invoked with as many of `($request, $action, $resource)` as its signature accepts. |
| 33 | |
| 34 | A top-level `permissionCallback` callable overrides per-action resolution for the Resource. A top-level `public => true` opens every registered action, including writes; use it only with a deliberately read-only `allow(['list', 'get'])` Resource. |
| 35 | |
| 36 | ## Defaults |
| 37 | |
| 38 | Without a policy: |
| 39 | |
| 40 | - table resources deny every action; |
| 41 | - CPT writes deny; |
| 42 | - CPT list/get allow only when the post type is publicly viewable, followed by item-level status/public/password visibility checks. |
| 43 | |
| 44 | Prefer explicit policy even where the CPT default is safe, so intent and OpenAPI are clear. |
| 45 | |
| 46 | ## Field policy in 1.1 |
| 47 | |
| 48 | ```php |
| 49 | ->fieldPolicy([ |
| 50 | 'featured' => ['write' => 'manage_options'], |
| 51 | 'author' => ['write' => ['edit_others_posts', 'manage_options']], |
| 52 | 'external_id' => ['write' => false], |
| 53 | 'tenant_id' => ['write' => static function ( |
| 54 | $request, |
| 55 | string $field, |
| 56 | string $action, |
| 57 | ?int $id |
| 58 | ): bool { |
| 59 | return can_write_tenant($request, $id); |
| 60 | }], |
| 61 | ]) |
| 62 | ``` |
| 63 | |
| 64 | Denied fields are not silently stripped in 1.1: |
| 65 | |
| 66 | - boolean `false` returns `400 validation_failed` with a field error; |
| 67 | - a failed capability/list/callback returns `403 forbidden` with field error details; |
| 68 | - allowed fields continue into coercion/sanitization. |
| 69 | |
| 70 | A field-policy callable can accept `($request, $field, $action, $id, $resource)`. Declare only the prefix needed. The action is `create` or `update`; PATCH also resolves as `update`. |
| 71 | |
| 72 | Only payload fields in the Resource's writable `fields()` set reach field policy. Unknown/read-only fields fail earlier. |
| 73 | |
| 74 | ## OpenAPI |
| 75 | |
| 76 | An action whose resolved static policy is explicitly public (`true` or top-level public) gets operation `security: []`. Callable policy source is never serialized into metadata; it is represented only as a safe callback marker where applicable. |
| 77 | |
| 78 | Do not assume a callable's runtime result can be inferred into OpenAPI. Document the security scheme explicitly when middleware/auth is required. |
| 79 | |
| 80 | ## Ownership |
| 81 | |
| 82 | Use `OwnedResourcePolicy::currentUserOwns()` for get/update/delete ownership checks. It resolves resource ID from the request, compares the owner to native current WP user, and optionally permits an admin bypass capability. List access still needs query-level row filtering; a permission callback alone cannot remove other users' rows from a collection. |
| 83 | |
| 84 | ## Review checklist |
| 85 | |
| 86 | - Declare which actions exist with `allow()` before reviewing permissions. |
| 87 | - Never use top-level `public => true` on a write-capable Resource unintentionally. |
| 88 | - Treat capability arrays as any-of, not all-of. |
| 89 | - Add object/row-level checks for get/update/delete and query-level filters for lists. |
| 90 | - Test denied fields and assert 400/403; do not expect silent removal. |
| 91 | - Keep policy callback work cheap because WordPress evaluates permission before the handler. |
| 92 | - Verify explicitly public actions emit `security: []` in OpenAPI. |
| 93 | |
| 94 | ## Related skills |
| 95 | |
| 96 | - Use `br-resource-cpt` or `br-resource-table` for source behavior. |
| 97 | - Use `br-owned-resource-guards` for ownership patterns. |
| 98 | - Use `br-write-schema` for validation after field authorization. |
| 99 | |
| 100 | ## References |
| 101 | |
| 102 | - Verified source paths: |
| 103 | - `src/Resource/ResourcePolicy.php` |
| 104 | - `src/Reso |