myCRM/.claude/ARCHITECTURE.md
olli 8a132d2fb9 feat: Implement ProjectTask module with full CRUD functionality
- Added ProjectTask entity with fields for name, description, budget, hour contingent, hourly rate, and total price.
- Created ProjectTaskRepository with methods for querying tasks by project and user access.
- Implemented ProjectTaskVoter for fine-grained access control based on user roles and project membership.
- Developed ProjectTaskSecurityListener to enforce permission checks during task creation.
- Introduced custom ProjectTaskProjectFilter for filtering tasks based on project existence.
- Integrated ProjectTask management in the frontend with Vue.js components, including CRUD operations and filtering capabilities.
- Added API endpoints for ProjectTask with appropriate security measures.
- Created migration for project_tasks table in the database.
- Updated documentation to reflect new module features and usage.
2025-11-14 17:12:40 +01:00

7.8 KiB

myCRM Architecture Overview

Quick Reference for Claude

Security Architecture

This codebase uses a multi-layered security system combining:

  1. Symfony Authentication - User login, password hashing via bcrypt
  2. Role-Based Access Control (RBAC) - Users → Roles → Modules → Permissions
  3. Custom Voters - Fine-grained entity-level authorization
  4. API Platform - Per-operation security expressions
  5. Query Extensions - Automatic filtering of accessible data

See /SECURITY_ARCHITECTURE.md for comprehensive details.

Entity Relationships - Quick Diagram

User
├── Standard Roles: ROLE_ADMIN, ROLE_USER (in 'roles' JSON column)
├── Application Roles: Collection<Role> (ManyToMany)
│   └── Role
│       └── Permissions: Collection<RolePermission> (OneToMany)
│           └── RolePermission
│               └── Module (the module being granted permission for)
└── Can belong to Project teams: Collection<Project> (ManyToMany)

Project
├── owner: User (REQUIRED)
├── teamMembers: Collection<User> (ManyToMany)
├── customer: Contact
├── status: ProjectStatus
└── gitRepositories: Collection<GitRepository> (OneToMany)

ProjectTask
├── project: Project (nullable - can be project-independent)
└── Budget & hourly tracking

GitRepository
├── project: Project (REQUIRED)
└── URL, branch, provider info

Contact
├── contactPersons: Collection<ContactPerson> (OneToMany)
└── Company info: name, address, tax numbers, etc.

ContactPerson
└── contact: Contact (ManyToOne - no separate permissions)

Permission Hierarchy

Level 1: Symfony Standard Roles

  • Checked in config/packages/security.yaml access_control
  • Used for basic authentication gates (API requires IS_AUTHENTICATED_FULLY)

Level 2: Module-Based Permissions

  • User.hasModulePermission(moduleCode, action) checks:
    • User's application roles
    • Each role's RolePermission for the module
    • The specific action flag (canView, canCreate, canEdit, etc.)

Level 3: Object-Level Voters

  • ModuleVoter - Module-level permissions (string subject or ModuleAwareInterface)
  • ProjectVoter - Ownership/team access to projects
  • ProjectTaskVoter - Project-based + admin-only for standalone tasks
  • GitRepositoryVoter - Project-based access

Level 4: Event Listeners

  • ProjectTaskSecurityListener - Additional prePersist check

Core Files to Know

Security Components:

  • src/Security/Voter/ModuleVoter.php - Routes to user.hasModulePermission()
  • src/Security/Voter/ProjectVoter.php - Checks project.owner === user or team
  • src/Security/Voter/ProjectTaskVoter.php - Combines project + admin checks
  • src/Security/Voter/GitRepositoryVoter.php - Tied to project access

Entities:

  • src/Entity/User.php - hasModulePermission() method
  • src/Entity/Role.php - permissions collection
  • src/Entity/RolePermission.php - defines per-module flags
  • src/Entity/Module.php - registry of modules
  • src/Entity/Project.php - hasAccess() checks owner/team
  • src/Entity/Interface/ModuleAwareInterface.php - getModuleName()

API Configuration:

  • config/packages/api_platform.yaml - Global API defaults
  • config/packages/security.yaml - Firewall, access_control
  • Entity #[ApiResource] attributes - Per-operation security expressions

API Filters

Most entities have API filters:

  • SearchFilter - Partial text search
  • BooleanFilter - Filter by boolean fields
  • DateFilter - Filter by date ranges
  • Custom ProjectTaskProjectFilter - hasProject filter for tasks

Authentication Methods

  1. Form Login - LoginFormAuthenticator - Email + password form
  2. OAuth - PocketIdAuthenticator - OIDC provider integration

Both update lastLoginAt in LoginSuccessListener.

Data Serialization

All APIs use serializer groups to control what's returned:

  • {entity}:read - Fields returned in GET
  • {entity}:write - Fields accepted in POST/PUT

Example: User with #[Groups(['user:read', 'project:read'])] appears in both user and project responses.

Key Patterns

Module-Based Permission Check:

is_granted('VIEW', 'contacts')  // Collection operation

ModuleVoter.supports()  true

ModuleVoter.voteOnAttribute('VIEW', 'contacts', $token)

user.hasModulePermission('contacts', 'view')

Object-Level Access Check:

is_granted('DELETE', $project)  // Delete operation

ProjectVoter.supports()  true

ProjectVoter.voteOnAttribute('DELETE', $project, $token)

project.getOwner() === $user

Project-Related Filtering:

ProjectAccessExtension applies to Project GetCollection
↓
Adds: WHERE owner = :user OR teamMembers = :user
↓
User sees only their projects

Adding New Features

New Module-Based Entity:

  1. Implement ModuleAwareInterface::getModuleName()
  2. Create Module record in database
  3. Add API security: new GetCollection(security: "is_granted('VIEW', 'module_code')")
  4. Create RolePermissions for existing roles

New Project-Related Entity:

  1. Add project: Project relationship
  2. Create custom Voter like ProjectTaskVoter
  3. Use in API: new Get(security: "is_granted('VIEW', object)")

New User Role:

  1. Create Role entity
  2. Add RolePermission records for each module
  3. Assign to users via user.addUserRole(role)

Common Security Attributes

Used in voters and API operations:

  • VIEW - Can read/list
  • CREATE - Can create new
  • EDIT - Can update
  • DELETE - Can remove
  • EXPORT - Can export data
  • MANAGE - Admin functions

Important Constants

Voter Attributes:

ProjectVoter::VIEW
ProjectVoter::EDIT
ProjectVoter::DELETE
ProjectVoter::CREATE
// Similar for other voters

API Platform Groups:

{entity}:read  → Normalization (GET)
{entity}:write → Denormalization (POST/PUT)

Symfony Standard Roles:

ROLE_USER   → Authenticated user
ROLE_ADMIN  → All permissions (bypasses voters)

Quick Debugging Checklist

  1. User can't access API endpoint?

    • Check access_control in security.yaml
    • Check API operation security expression
    • Check voter implementation
  2. User has role but can't perform action?

    • Verify RolePermission exists for that role+module
    • Check the specific permission flag (canView, etc.)
    • Verify module code matches (case-sensitive!)
  3. Non-admin accessing admin resource?

    • Check ROLE_ADMIN bypass in voter
    • Verify voter supports() returns true
    • Look at voteOnAttribute() logic
  4. Permission check in code?

    // Get current user
    $user = $this->getUser(); // null if not authenticated
    
    // Check module permission
    if ($user->hasModulePermission('contacts', 'create')) {
        // Allow
    }
    
    // In Twig
    {% if app.user.hasModulePermission('contacts', 'view') %}...{% endif %}
    
    // In security expressions
    is_granted('CREATE', 'contacts')
    is_granted('EDIT', $contact)
    

Files Changed Recently

Check git status to see security-related changes:

  • src/Security/Voter/*.php - Voter implementations
  • src/Entity/*.php - Entity definitions
  • src/EventListener/*.php - Security listeners
  • config/packages/security.yaml - Auth configuration

Testing Security

Unit Testing:

  • Create user and project fixtures
  • Test voter returns ALLOW/DENY/ABSTAIN correctly
  • Test edge cases (null owner, empty team, etc.)

Integration Testing:

  • Make API calls as different users
  • Verify 403 Forbidden for unauthorized access
  • Verify 200 OK for authorized access
  • Check returned data respects serializer groups

See Also

  • /SECURITY_ARCHITECTURE.md - Complete security documentation
  • /docs/PERMISSIONS.md - User management and permissions guide
  • config/packages/security.yaml - Security configuration
  • API Platform docs: https://api-platform.com/docs/core/security/