feat: Implement SystemRoleProtection validator to prevent modification of system roles

This commit is contained in:
olli 2025-11-08 17:15:30 +01:00
parent 0181f72208
commit 9e95a83325
4 changed files with 142 additions and 0 deletions

95
SECURITY.md Normal file
View File

@ -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`

View File

@ -9,6 +9,7 @@ use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put; use ApiPlatform\Metadata\Put;
use ApiPlatform\Metadata\Delete; use ApiPlatform\Metadata\Delete;
use App\Repository\RoleRepository; use App\Repository\RoleRepository;
use App\Validator\SystemRoleProtection;
use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection; use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM; use Doctrine\ORM\Mapping as ORM;
@ -16,6 +17,7 @@ use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: RoleRepository::class)] #[ORM\Entity(repositoryClass: RoleRepository::class)]
#[ORM\Table(name: 'roles')] #[ORM\Table(name: 'roles')]
#[SystemRoleProtection(groups: ['role:write'])]
#[ApiResource( #[ApiResource(
operations: [ operations: [
new GetCollection(stateless: false), new GetCollection(stateless: false),

View File

@ -0,0 +1,16 @@
<?php
namespace App\Validator;
use Symfony\Component\Validator\Constraint;
#[\Attribute]
class SystemRoleProtection extends Constraint
{
public string $message = 'System-Rollen können nicht bearbeitet oder gelöscht werden.';
public function getTargets(): string
{
return self::CLASS_CONSTRAINT;
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace App\Validator;
use App\Entity\Role;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
use Symfony\Component\Validator\Exception\UnexpectedValueException;
class SystemRoleProtectionValidator extends ConstraintValidator
{
public function validate(mixed $value, Constraint $constraint): void
{
if (!$constraint instanceof SystemRoleProtection) {
throw new UnexpectedTypeException($constraint, SystemRoleProtection::class);
}
if (!$value instanceof Role) {
throw new UnexpectedValueException($value, Role::class);
}
// Check if this is an existing system role being modified
if ($value->getId() !== null && $value->isSystem()) {
$this->context->buildViolation($constraint->message)
->addViolation();
}
}
}