From 9e95a833258778cdd5a54f059cacef8886ff2fbc Mon Sep 17 00:00:00 2001 From: olli Date: Sat, 8 Nov 2025 17:15:30 +0100 Subject: [PATCH] feat: Implement SystemRoleProtection validator to prevent modification of system roles --- SECURITY.md | 95 +++++++++++++++++++ src/Entity/Role.php | 2 + src/Validator/SystemRoleProtection.php | 16 ++++ .../SystemRoleProtectionValidator.php | 29 ++++++ 4 files changed, 142 insertions(+) create mode 100644 SECURITY.md create mode 100644 src/Validator/SystemRoleProtection.php create mode 100644 src/Validator/SystemRoleProtectionValidator.php diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..ce57daa --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,95 @@ +# Security Recommendations for myCRM + +## Implemented Security Measures + +### 1. Authentication & Authorization +- ✅ Session-based authentication (stateless: false) +- ✅ Role-based access control (RBAC) +- ✅ API endpoints protected with `is_granted()` checks +- ✅ User can only edit own profile or requires ROLE_ADMIN +- ✅ System roles protected via SystemRoleProtection validator + +### 2. Password Security +- ✅ Passwords hashed via Symfony PasswordHasher +- ✅ Plain passwords erased after hashing +- ✅ No passwords in serialization groups + +### 3. XSS Prevention +- ✅ Vue.js automatic escaping +- ✅ No v-html or innerHTML usage +- ✅ All user input properly escaped + +### 4. CSRF Protection +- ✅ Session-based API (SameSite cookies) +- ✅ credentials: 'same-origin' in fetch calls + +## Recommended Additional Measures + +### 1. Rate Limiting +Consider implementing rate limiting for API endpoints: +```bash +composer require symfony/rate-limiter +``` + +Configuration example in `config/packages/rate_limiter.yaml`: +```yaml +framework: + rate_limiter: + api_login: + policy: 'sliding_window' + limit: 5 + interval: '1 minute' + api_general: + policy: 'fixed_window' + limit: 100 + interval: '1 hour' +``` + +### 2. HTTPS Only (Production) +Ensure HTTPS is enforced in production: +```yaml +# config/packages/framework.yaml (when@prod) +framework: + session: + cookie_secure: true + cookie_samesite: 'strict' +``` + +### 3. Content Security Policy +Add CSP headers via `nelmio/security-bundle`: +```bash +composer require nelmio/security-bundle +``` + +### 4. Input Validation +- ✅ Email validation (Symfony built-in) +- ✅ Required field validation +- Consider adding: max length validation, sanitization + +### 5. Audit Logging +Consider logging sensitive operations: +- User creation/deletion +- Role assignment changes +- Permission modifications + +### 6. Database Security +- ✅ Prepared statements via Doctrine (SQL injection protected) +- ✅ Unique constraints on email/role+module combinations +- Consider: Database encryption for sensitive fields + +### 7. Error Handling +Current: Errors exposed in dev mode +Production: Ensure debug mode is disabled and errors are logged securely + +## Security Checklist for Deployment + +- [ ] Set `APP_ENV=prod` and `APP_DEBUG=0` +- [ ] Enable HTTPS with valid SSL certificate +- [ ] Set secure session cookie settings +- [ ] Implement rate limiting +- [ ] Set up security headers (CSP, X-Frame-Options, etc.) +- [ ] Regular dependency updates (`composer update`) +- [ ] Database backups configured +- [ ] Error logging to secure location +- [ ] Monitor authentication failures +- [ ] Review and rotate secrets in `.env` diff --git a/src/Entity/Role.php b/src/Entity/Role.php index e1594ce..de2830a 100644 --- a/src/Entity/Role.php +++ b/src/Entity/Role.php @@ -9,6 +9,7 @@ use ApiPlatform\Metadata\Post; use ApiPlatform\Metadata\Put; use ApiPlatform\Metadata\Delete; use App\Repository\RoleRepository; +use App\Validator\SystemRoleProtection; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; @@ -16,6 +17,7 @@ use Symfony\Component\Serializer\Annotation\Groups; #[ORM\Entity(repositoryClass: RoleRepository::class)] #[ORM\Table(name: 'roles')] +#[SystemRoleProtection(groups: ['role:write'])] #[ApiResource( operations: [ new GetCollection(stateless: false), diff --git a/src/Validator/SystemRoleProtection.php b/src/Validator/SystemRoleProtection.php new file mode 100644 index 0000000..3aa132e --- /dev/null +++ b/src/Validator/SystemRoleProtection.php @@ -0,0 +1,16 @@ +getId() !== null && $value->isSystem()) { + $this->context->buildViolation($constraint->message) + ->addViolation(); + } + } +}