Skip to content

Roles & Permissions

MyWarranties uses role-based access control (RBAC) to manage user permissions and secure API endpoints.

User Roles

ROLE_CUSTOMER

Default role for customers managing their warranties.

Permissions:

  • Create, read, update, delete own products
  • Create warranty claims
  • View and chat in own claims
  • Close own claims
  • Upload files and attachments to claims
  • View own profile and update it
  • Receive notifications

Cannot:

  • View other customers' products or claims
  • View supplier or admin features
  • Assign claims to suppliers
  • View customer details of other users

ROLE_SUPPLIER

Role for warranty suppliers handling customer claims.

Permissions:

  • View claims assigned to them
  • Chat with customers in claims
  • Close claims (assigned to them)
  • Upload files and attachments to claims
  • View customer details (only for their open claims)
  • Access supplier dashboard
  • Update claim status (open, in_progress, resolved, closed)

Cannot:

  • Create products for customers
  • View unassigned claims
  • Access admin features
  • View customer details for closed claims
  • Manage user roles

ROLE_RESELLER

Role for resellers who sell products and handle warranty claims.

Permissions:

  • All ROLE_SUPPLIER permissions
  • Register products for customers
  • Create products on behalf of customers
  • View products sold by them
  • Handle warranty claims for products they sold
  • Access reseller dashboard
  • View sales reports

Cannot:

  • View products sold by other resellers
  • Access admin features
  • Manage user roles

ROLE_ADMIN

Full system administration access.

Permissions:

  • All ROLE_RESELLER and ROLE_SUPPLIER permissions
  • View all products across all users
  • View and manage all claims
  • Create, update, delete users
  • Assign and modify user roles
  • Assign claims to suppliers
  • View all customer details
  • Access all endpoints
  • View system analytics
  • Export reports
  • Manage system configuration

Checking Roles

In Controllers

php
use Symfony\Component\Security\Http\Attribute\IsGranted;

#[IsGranted('ROLE_USER')]
class ProductController extends AbstractController
{
    #[Route('/api/products', methods: ['GET'])]
    public function index(): JsonResponse
    {
        // Only authenticated users can access
    }

    #[IsGranted('ROLE_ADMIN')]
    #[Route('/api/admin/products', methods: ['GET'])]
    public function adminIndex(): JsonResponse
    {
        // Only admins can access
    }
}

Manual Check

php
if (!$this->isGranted('ROLE_ADMIN')) {
    throw new AccessDeniedException('Admin access required');
}

Check in Twig (if using)

twig
{% if is_granted('ROLE_ADMIN') %}
    <a href="/admin">Admin Panel</a>
{% endif %}

Claim Workflow & Permissions

Creating Claims

  1. Customer creates a claim for their product
  2. Claim is automatically assigned to product's supplier (if known)
  3. Both customer and supplier receive notification

Viewing Claims

  • Customer: Can see their own claims
  • Supplier: Can see claims assigned to them
  • Reseller: Can see claims for products they sold
  • Admin: Can see all claims

Chatting in Claims

  • Customer: Can send messages in their claims
  • Supplier: Can send messages in assigned claims
  • Both can see full conversation history
  • Real-time updates via Mercure

Uploading Files

  • Customer: Can upload files (photos, documents) to their claims
  • Supplier: Can upload files to assigned claims
  • Supported formats: JPG, PNG, PDF
  • Max file size: 10MB per file

Closing Claims

  • Customer: Can close their own claims
  • Supplier: Can close assigned claims
  • Admin: Can close any claim
  • Once closed, claim becomes read-only
  • Closed claims can be reopened by admin only

Viewing Customer Details

Supplier can view when claim is open:

  • Customer name
  • Customer email
  • Customer phone (if provided)
  • Customer address (if provided)

Supplier CANNOT view when claim is closed:

  • Customer details are hidden after claim closes
  • Only claim history remains visible

Role Hierarchy

Roles do NOT inherit from each other - each role has specific permissions:

yaml
# config/packages/security.yaml
security:
    role_hierarchy:
        ROLE_ADMIN: [ROLE_CUSTOMER, ROLE_SUPPLIER, ROLE_RESELLER]

This means:

  • Each role is independent
  • Admin has access to all role permissions
  • Supplier and Reseller are separate roles
  • Users can have multiple roles (e.g., both CUSTOMER and SUPPLIER)

API Endpoints by Role

Public (No Authentication)

EndpointMethodDescription
/api/send-codePOSTSend verification code
/api/verify-codePOSTVerify code and login

ROLE_CUSTOMER

EndpointMethodDescription
/api/meGETGet user profile
/api/mePUTUpdate profile
/api/productsGETList user's products
/api/productsPOSTCreate product
/api/products/{id}GET, PUT, DELETEManage product
/api/claimsGETList user's claims
/api/claimsPOSTCreate claim
/api/claims/{id}GETView claim details
/api/claims/{id}/closePOSTClose own claim
/api/claims/messages/{id}GETView claim messages
/api/claims/messages/{id}POSTSend message + upload files

ROLE_SUPPLIER

EndpointMethodDescription
/api/claims/assignedGETGet assigned claims
/api/claims/{id}GETView assigned claim (includes customer details)
/api/claims/{id}/closePOSTClose assigned claim
/api/claims/{id}/statusPUTUpdate claim status
/api/claims/messages/{id}GETView claim messages
/api/claims/messages/{id}POSTSend message + upload files
/api/supplier/dashboardGETSupplier metrics

ROLE_RESELLER

All ROLE_SUPPLIER endpoints plus:

EndpointMethodDescription
/api/productsPOSTCreate product for customer
/api/reseller/productsGETProducts sold by reseller
/api/reseller/claimsGETClaims for reseller's products
/api/reseller/dashboardGETReseller metrics

ROLE_ADMIN

All endpoints plus:

EndpointMethodDescription
/api/admin/productsGETAll products
/api/admin/claimsGETAll claims
/api/admin/claims/{id}/reopenPOSTReopen closed claim
/api/admin/usersGET, POSTManage users
/api/admin/users/{id}GET, PUT, DELETEUser details
/api/admin/users/{id}/rolesPUTUpdate roles
/api/admin/reportsGETSystem reports

User Account Deletion

When a user deletes their account:

What Gets Deleted

  • User profile information
  • Login credentials
  • Personal data (name, email, phone)
  • Notification preferences

What Gets Kept

  • All warranty claims (including closed ones)
  • Claim messages and conversation history
  • Product information (anonymized)
  • Uploaded files (receipts, photos)

Anonymization

Deleted user accounts are anonymized:

  • User email becomes: deleted-user-{id}@deleted.local
  • User name becomes: [Deleted User]
  • Claims remain visible with anonymized customer info
  • Suppliers can still access claim history

Why Keep Claims?

Claims contain important business and legal information:

  • Warranty dispute history
  • Communication records
  • Resolution documentation
  • Statistical data for suppliers

Technical Implementation

php
// User deletion does NOT cascade to claims
$user->setEmail("deleted-user-{$user->getId()}@deleted.local");
$user->setName('[Deleted User]');
$user->setRoles(['ROLE_DELETED']);
$user->setDeletedAt(new \DateTime());
$entityManager->flush();

// Claims remain intact with foreign key to user

Assigning Roles

Via Console Command

bash
# Add supplier role
php bin/console app:user:roles user@example.com --add=ROLE_SUPPLIER

# Remove supplier role
php bin/console app:user:roles user@example.com --remove=ROLE_SUPPLIER

# Set specific roles (removes all others)
php bin/console app:user:roles user@example.com --set=ROLE_ADMIN

# List user roles
php bin/console app:user:roles user@example.com --list

Via API (Admin Only)

http
PUT /api/admin/users/123/roles
Authorization: Bearer <admin-jwt-token>
Content-Type: application/json

{
  "roles": ["ROLE_USER", "ROLE_SUPPLIER"]
}

Response:

json
{
  "id": 123,
  "email": "user@example.com",
  "roles": ["ROLE_USER", "ROLE_SUPPLIER"]
}

Programmatically

php
use App\Entity\User;

$user = $userRepository->find($userId);
$user->setRoles(['ROLE_USER', 'ROLE_SUPPLIER']);
$entityManager->flush();

Access Control Examples

Product Access

php
// Users can only access their own products
if ($product->getUser() !== $this->getUser()) {
    throw new AccessDeniedException('Not your product');
}

// Unless they're an admin
if (!$this->isGranted('ROLE_ADMIN')) {
    if ($product->getUser() !== $this->getUser()) {
        throw new AccessDeniedException('Not your product');
    }
}

Claim Access

php
// Customers can access their own claims
if ($claim->getCustomer() === $this->getUser()) {
    return $claim;
}

// Suppliers can access claims assigned to them
if ($this->isGranted('ROLE_SUPPLIER') && $claim->getSupplier() === $this->getUser()) {
    // Include customer details only if claim is open
    if ($claim->getStatus() !== 'closed') {
        $claim->setCustomerDetails([
            'name' => $claim->getCustomer()->getName(),
            'email' => $claim->getCustomer()->getEmail(),
            'phone' => $claim->getCustomer()->getPhone(),
            'address' => $claim->getCustomer()->getAddress()
        ]);
    }
    return $claim;
}

// Resellers can access claims for products they sold
if ($this->isGranted('ROLE_RESELLER')) {
    $product = $claim->getProduct();
    if ($product->getReseller() === $this->getUser()) {
        return $claim;
    }
}

// Admins can access all claims
if ($this->isGranted('ROLE_ADMIN')) {
    return $claim;
}

throw new AccessDeniedException('Cannot access this claim');

Closing Claims

php
// Check if user can close this claim
public function canCloseClaim(WarrantyClaim $claim, User $user): bool
{
    // Customer can close their own claims
    if ($claim->getCustomer() === $user) {
        return true;
    }

    // Supplier can close assigned claims
    if ($user->hasRole('ROLE_SUPPLIER') && $claim->getSupplier() === $user) {
        return true;
    }

    // Admin can close any claim
    if ($user->hasRole('ROLE_ADMIN')) {
        return true;
    }

    return false;
}

File Upload Access

php
// Check if user can upload files to claim
public function canUploadToClaim(WarrantyClaim $claim, User $user): bool
{
    // Customer can upload to their claims
    if ($claim->getCustomer() === $user) {
        return true;
    }

    // Supplier can upload to assigned claims
    if ($user->hasRole('ROLE_SUPPLIER') && $claim->getSupplier() === $user) {
        return true;
    }

    // Admin can upload to any claim
    if ($user->hasRole('ROLE_ADMIN')) {
        return true;
    }

    return false;
}

Error Responses

401 Unauthorized

User is not authenticated (no JWT token or invalid token).

json
{
  "error": "Unauthorized",
  "message": "Invalid JWT Token"
}

403 Forbidden

User is authenticated but lacks required role.

json
{
  "error": "Forbidden",
  "message": "Access Denied. Requires ROLE_ADMIN."
}

Best Practices

  1. Principle of Least Privilege

    • Assign minimum required roles
    • Use specific role checks, not just "authenticated"
  2. Check Ownership

    • Always verify resource ownership
    • Don't rely solely on roles
  3. Multiple Roles

    • Users can have multiple roles
    • Check for any applicable role, not all
  4. Frontend Guards

    • Hide/disable UI elements based on roles
    • Always validate on backend too
  5. Audit Logging

    • Log role changes
    • Track admin actions
    • Monitor suspicious activity

Testing Roles

In PHPUnit

php
public function testAdminCanAccessAllProducts(): void
{
    $admin = $this->createUser('admin@example.com', ['ROLE_ADMIN']);
    $client = $this->createAuthenticatedClient($admin);

    $client->request('GET', '/api/admin/products');
    $this->assertResponseIsSuccessful();
}

public function testUserCannotAccessAdminEndpoint(): void
{
    $user = $this->createUser('user@example.com', ['ROLE_USER']);
    $client = $this->createAuthenticatedClient($user);

    $client->request('GET', '/api/admin/products');
    $this->assertResponseStatusCodeSame(403);
}

Common Scenarios

Multi-Tenant Access

php
// User can be both customer and supplier
$user->setRoles(['ROLE_USER', 'ROLE_SUPPLIER']);

// Show different dashboards based on active role
if ($this->isGranted('ROLE_SUPPLIER')) {
    return $this->render('supplier/dashboard.html.twig');
}
return $this->render('user/dashboard.html.twig');

Temporary Elevation

php
// Grant temporary admin access
$user->setRoles([...$user->getRoles(), 'ROLE_ADMIN']);
// After specific task
$user->setRoles(array_diff($user->getRoles(), ['ROLE_ADMIN']));

Role-Based Views

php
// Return different data based on role
if ($this->isGranted('ROLE_ADMIN')) {
    return $this->json($product, context: ['groups' => ['product:admin']]);
}
return $this->json($product, context: ['groups' => ['product:user']]);

Security Considerations

  • Never trust client-side role checks - Always validate on server
  • JWT tokens contain roles - Changing roles requires new token
  • Cache invalidation - Clear user cache when roles change
  • Session management - Force re-login after role changes
  • Audit trail - Log all role modifications

MyWarranties - Warranty Management System