- 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.
269 lines
7.8 KiB
Markdown
269 lines
7.8 KiB
Markdown
# 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:**
|
|
```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/
|