跳到正文
cccskills
免费在 GitHub 公开

waaseyaa:access-control

Use when working with access policies, field access, authorization middleware, gates, route access control, AccessResult semantics, permission handlers, or files in packages/access/, packages/routing/src/AccessChecker.php, packages/user/src/Middleware/. Covers entity-level access (deny-by-default via isAllowed), field-level access (open-by-default via !isForbidden), the Gate system, route options (_public, _permission, _role, _gate), and the SessionMiddleware/AuthorizationMiddleware pipeline in public/index.php.

中文简介正在准备中。当前显示原文说明。

查看安装方法

包含的文件(2)

  • SKILL.md11.6 KB
  • source.json1.0 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

Access Control Specialist

Scope

This skill covers the Waaseyaa access control system:

  • Entity-level access: AccessPolicyInterface, EntityAccessHandler, AccessResult
  • Field-level access: FieldAccessPolicyInterface, checkFieldAccess(), filterFields()
  • Route-level access: AccessChecker, route options, AuthorizationMiddleware
  • Gate system: Gate, GateInterface, PolicyAttribute, AccessDeniedException
  • Session resolution: SessionMiddleware, AccountInterface
  • Permission registry: PermissionHandler, PermissionHandlerInterface
  • API integration: ResourceSerializer field filtering, SchemaPresenter access annotations
  • Discovery: #[AccessPolicy] attribute, manifest compilation

Key Interfaces

AccessPolicyInterface

File: packages/access/src/AccessPolicyInterface.php

interface AccessPolicyInterface
{
    public function access(EntityInterface $entity, string $operation, AccountInterface $account): AccessResult;
    public function createAccess(string $entityTypeId, string $bundle, AccountInterface $account): AccessResult;
    public function appliesTo(string $entityTypeId): bool;
}

Operations for access(): 'view', 'update', 'delete'.

FieldAccessPolicyInterface

File: packages/access/src/FieldAccessPolicyInterface.php

interface FieldAccessPolicyInterface
{
    public function fieldAccess(
        EntityInterface $entity,
        string $fieldName,
        string $operation, // 'view' or 'edit'
        AccountInterface $account,
    ): AccessResult;
}

Must be implemented alongside AccessPolicyInterface. EntityAccessHandler finds field policies via instanceof FieldAccessPolicyInterface.

AccountInterface

File: packages/access/src/AccountInterface.php

interface AccountInterface
{
    public function id(): int|string;
    public function hasPermission(string $permission): bool;
    public function getRoles(): array; // string[]
    public function isAuthenticated(): bool;
}

AccessResult

File: packages/access/src/AccessResult.php

Three states: AccessResult::allowed(), AccessResult::neutral(), AccessResult::forbidden().

Combination operators:

  • orIf(): Forbidden wins, either Allowed yields Allowed. Used by EntityAccessHandler.
  • andIf(): Forbidden wins, both must be Allowed. Used by AccessChecker for route requirements.

EntityAccessHandler

File: packages/access/src/EntityAccessHandler.php

class EntityAccessHandler
{
    public function __construct(array $policies = []);
    public function addPolicy(AccessPolicyInterface $policy): void;
    public function check(EntityInterface $entity, string $operation, AccountInterface $account): AccessResult;
    public function checkCreateAccess(string $entityTypeId, string $bundle, AccountInterface $account): AccessResult;
    public function checkFieldAccess(EntityInterface $entity, string $fieldName, string $operation, AccountInterface $account): AccessResult;
    public function filterFields(EntityInterface $entity, array $fieldNames, string $operation, AccountInterface $account): array;
}

GateInterface

File: packages/access/src/Gate/GateInterface.php

interface GateInterface
{
    public function allows(string $ability, mixed $subject, ?object $user = null): bool;
    public function denies(string $ability, mixed $subject, ?object $user = null): bool;
    public function authorize(string $ability, mixed $subject, ?object $user = null): void;
}

AccessChecker

File: packages/routing/src/AccessChecker.php

final class AccessChecker
{
    public function __construct(private readonly ?GateInterface $gate = null);
    public function check(Route $route, AccountInterface $account): AccessResult;
    public static function applyGateToRoute(Route $route, string $ability, mixed $subject = null): void;
}

Route options: _public (bool), _permission (string), _role (string, comma-separated), _gate (array).

Architecture

Authorization Pipeline

Request -> SessionMiddleware -> AuthorizationMiddleware -> Final Handler -> Response
  • SessionMiddleware (packages/user/src/Middleware/SessionMiddleware.php): reads $_SESSION['waaseyaa_uid'], loads User entity, falls back to AnonymousUser, sets _account on request attributes.
  • AuthorizationMiddleware (packages/access/src/Middleware/AuthorizationMiddleware.php): reads _account and _route_object from request, delegates to AccessChecker::check(), returns 403 JSON:API response on Forbidden.

Asymmetric Access Semantics

This is the most critical architectural detail:

LevelCheck MethodDefault StanceInterpretation
Entity$result->isAllowed()Deny unless grantedNeutral = denied
Field!$result->isForbidden()Allow unless deniedNeutral = accessible

Entity access: deny-by-default. A policy must return Allowed. Field access: open-by-default. Only explicit Forbidden restricts.

Package Dependencies

access (layer 1) -- owns AccountInterface, AccessPolicyInterface, FieldAccessPolicyInterface, EntityAccessHandler
    |
    +-- Does NOT depend on user package
    |
user (layer 1) -- owns User, AnonymousUser, SessionMiddleware
    |
    +-- Depends on access (for AccountInterface)
    |
routing (layer 4) -- owns AccessChecker
    |
    +-- Depends on access (for AccessResult, AccountInterface, GateInterface)

AccountInterface lives in access, not user. This prevents circular dependencies. Always type-hint AccountInterface, not AnonymousUser.

Paired Nullable Parameters

ResourceSerializer::serialize() and SchemaPresenter::present() accept ?EntityAccessHandler + ?AccountInterface. Both must be non-null or both null.

if ($handler !== null && $account !== null) {
    // Apply field filtering
}

x-access-restricted

JSON Schema extension marking fields viewable but not editable. The admin SPA reads this to show disabled widgets. Distinct from system readOnly (id, uuid) which hides the field entirely.

{
  "status": {
    "type": "boolean",
    "readOnly": true,
    "x-access-restricted": true
  }
}

Common Mistakes

Wrong access check method for the level

// WRONG: using isAllowed() for field check
if ($handler->checkFieldAccess($entity, 'title', 'view', $account)->isAllowed()) { ... }

// CORRECT: field access uses !isForbidden()
if (!$handler->checkFieldAccess($entity, 'title', 'view', $account)->isForbidden()) { ... }

Circular dependency: access depending on user

// WRONG: importing from user package into access package
use Waaseyaa\User\AnonymousUser;

// CORRECT: type-hint the interface from access package
use Waaseyaa\Access\AccountInterface;

Trying to mock final classes or intersection types

// WRONG: PHPUnit can't mock final class or intersection types
$policy = $this->createMock(AccessPolicyInterface::class); // may fail if final
$policy = $this->createMock(AccessPolicyInterface::class & FieldAccessPolicyInterface::class); // fails

// CORRECT: use anonymous class
$policy = new class implements AccessPolicyInterface, FieldAccessPolicyInterface {
    public function access(EntityInterface $entity, string $operation, AccountInterface $account): AccessResult {
        return AccessResult::allowed();
    }
    public function createAccess(string $entityTypeId, string $bundle, AccountInterface $account): AccessResult {
        return AccessResult::neutral();
    }
    public function appliesTo(string $entityTypeId): bool {
        return true;
    }
    public function fieldAccess(EntityInterface $entity, string $fieldName, string $operation, AccountInterface $account): AccessResult {
        return AccessResult::neutral();
    }
};

Forgetting that only FieldAccessPolicyInterface implementations participate

EntityAccessHandler skips policies that do not implement FieldAccessPolicyInterface during field access checks. An AccessPolicyInterface-only class will never have fieldAccess() called.

Double entity creation in access checks

When checking field access before persisting a new entity, create the entity once and reuse it for both the access check and the save. Do not create a throwaway temp entity for the access check.

Missing paired nullable parameters

Passing EntityAccessHandler without AccountInterface (or vice versa) results in no filtering. Both must be provided.

Layer discipline for discovery

Foundation (layer 0) must never import from access (layer 1). Policy attribute scanning in PackageManifestCompiler uses string constants:

private const POLICY_ATTRIBUTE = 'Waaseyaa\\Access\\Gate\\PolicyAttribute';
// NOT: use Waaseyaa\Access\Gate\PolicyAttribute;

Gate naming convention mismatch

Gate resolves NodePolicy to entity type node via PascalCase-to-snake_case conversion. Use str_replace('_', '', ucwords($name, '_')) for the reverse (snake_case to PascalCase), not ucfirst().

Testing Patterns

Unit Tests

Use real instances, not mocks, for EntityAccessHandler (not a final class but policies often are):

$handler = new EntityAccessHandler([$policy1, $policy2]);
$result = $handler->check($entity, 'view', $account);
self::assertTrue($result->isAllowed());

Anonymous Classes for Policies

$policy = new class implements AccessPolicyInterface, FieldAccessPolicyInterface {
    // ... implement all methods with inline logic
};

In-Memory Infrastructure

$db = PdoDatabase::createSqlite(); // :memory: SQLite
$storage = new InMemoryEntityStorage(); // from Waaseyaa\Api\Tests\Fixtures

Integration Test Location

tests/Integration/Phase6/FieldAccessIntegrationTest.php
tests/Integration/Phase11/AuthorizationPipelineTest.php

PHPUnit Attributes

#[Test]
#[CoversClass(EntityAccessHandler::class)]
public function checkFieldAccessReturnsForbiddenForRestrictedField(): void

Use #[CoversNothing] for integration tests.

Related Specs

  • docs/specs/access-control.md -- Entity-level access, route access, Gate system, authorization pipeline
  • docs/specs/field-access.md -- Field-level access, asymmetric semantics, x-access-restricted, testing patterns

Key Files

packages/access/src/AccessPolicyInterface.php
packages/access/src/FieldAccessPolicyInterface.php
packages/access/src/AccessResult.php
packages/access/src/AccessStatus.php
packages/access/src/EntityAccessHandler.php
packages/access/src/AccountInterface.php
packages/access/src/PermissionHandler.php
packages/access/src/PermissionHandlerInterface.php
packages/access/src/Attribute/AccessPolicy.php
packages/access/src/Gate/Gate.php
packages/access/src/Gate/GateInterface.php
packages/access/src/Gate/PolicyAttribute.php
packages/access/src/Gate/AccessDeniedException.php
packages/access/src/Middleware/AuthorizationMiddleware.php
packages/routing/src/AccessChecker.php
packages/user/src/Middleware/SessionMiddleware.php
packages/api/src/ResourceSerializer.php
packages/api/src/JsonApiController.php
packages/api/src/Schema/SchemaPresenter.php
public/index.php

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

007

免费

Security audit, hardening, threat modeling (STRIDE/PASTA), Red/Blue Team, OWASP checks, code review, incident response, and infrastructure security for any project.

中文简介正在准备中。当前显示原文说明。

aibot88/sec_skill_store52026年5月27日 更新

Guides the creation of agile user stories and Gherkin feature files. Use when the user wants to create a user story, write acceptance criteria, define Gherkin scenarios, or author BDD feature files. This should trigger for requests such as Create a user story; Write a user story; I need to write a user story. Part of cursor-rules-java project

中文简介正在准备中。当前显示原文说明。

aibot88/sec_skill_store52026年5月27日 更新

Facilitates conversational discovery to create Architectural Decision Records (ADRs) for non-functional requirements using the ISO/IEC 25010:2023 quality model. Use when the user wants to document quality attributes, NFR decisions, security/performance/scalability architecture, or design systems with measurable quality criteria. This should trigger for requests such as Create ADR for Non-functional requirements; Document Non-functional requirements; Capture Non-functional requirements; Generate Non-functional requirements in an ADR. Part of cursor-rules-java project

中文简介正在准备中。当前显示原文说明。

aibot88/sec_skill_store52026年5月27日 更新

Run a health check on an existing project: dependency audit, security scan, test runner detection, CI/CD evaluation, and missing configuration analysis. Maps the three execution gates (pre/in/post) from /10x-bootstrapper to an assessment framework for existing codebases. Reads optional context/foundation/stack-assessment.md from /10x-stack-assess to focus checks on identified gaps. Writes context/foundation/health-check.md with findings, prioritized fixes, and an agent-readiness verdict. Use when the user has an existing project and wants to verify its health before working with an agent. Trigger phrases: "health check", "check my project", "audit my project", "is my project healthy", "sprawdź projekt", "audyt projektu", "health-check", "project health". Use AFTER /10x-stack-assess (brownfield chain), BEFORE agent onboarding (m1-l4).

中文简介正在准备中。当前显示原文说明。

aibot88/sec_skill_store52026年5月27日 更新

10x-team

免费

You MUST use this when building projects end-to-end. Orchestrates all 12 team roles — automatically switches between CTO, architect, PM, engineers, SRE, security, DBA, QA, and EM based on the current phase of work. Starts with brainstorming before any implementation.

中文简介正在准备中。当前显示原文说明。

aibot88/sec_skill_store52026年5月27日 更新

Use when you need to add or configure Maven plugins in your pom.xml — including quality tools (enforcer, surefire, failsafe, jacoco, pitest, spotbugs, pmd), security scanning (OWASP), code formatting (Spotless), version management, container image build (Jib), build information tracking, and benchmarking (JMH) — through a consultative, modular step-by-step approach that only adds what you actually need. This should trigger for requests such as Add Maven plugins in pom.xml; Improve Maven plugins in pom.xml. Part of cursor-rules-java project

中文简介正在准备中。当前显示原文说明。

aibot88/sec_skill_store52026年5月27日 更新

查看 aibot88 的全部技能

报告此技能的问题