# 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 (ManyToMany) │ └── Role │ └── Permissions: Collection (OneToMany) │ └── RolePermission │ └── Module (the module being granted permission for) └── Can belong to Project teams: Collection (ManyToMany) Project ├── owner: User (REQUIRED) ├── teamMembers: Collection (ManyToMany) ├── customer: Contact ├── status: ProjectStatus └── gitRepositories: Collection (OneToMany) ProjectTask ├── project: Project (nullable - can be project-independent) └── Budget & hourly tracking GitRepository ├── project: Project (REQUIRED) └── URL, branch, provider info Contact ├── contactPersons: Collection (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:** ```php is_granted('VIEW', 'contacts') // Collection operation ↓ ModuleVoter.supports() → true ↓ ModuleVoter.voteOnAttribute('VIEW', 'contacts', $token) ↓ user.hasModulePermission('contacts', 'view') ``` **Object-Level Access Check:** ```php 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:** ```php 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?** ```php // 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/