# Security & Permissions

This guide explains how to add new permissions and update the Permission Seeder, as well as how to implement role-based access control (RBAC) in the application.

## Overview

The application uses **Spatie Laravel Permissions** package with custom extensions for:
- Translatable permission titles (Arabic/English)
- Category-based permission grouping
- Company-specific role isolation
- Section-based access (Accounting/Procurement)

## Permission System Architecture

### Permission Model

**Location:** `app/Domains/Core/Permission/Models/Permission.php`

```php
class Permission extends BasePermission
{
    use HasTranslations;

    public array $translatable = ["title"];

    // Key attributes:
    // - name: Permission identifier (e.g., 'module.resource.action')
    // - title: Translatable display name (JSON)
    // - group_title: Logical grouping (e.g., 'module.resource')
    // - category: Broader categorization (e.g., 'Accounting', 'Maintenance')
    // - guard_name: Authentication guard (default: 'web')
}
```

### Naming Convention

Permissions follow a **dot-notation pattern**:

```
{domain}.{resource}.{action}
```

**Examples:**
- `maintenance.assets.view`
- `maintenance.assets.create`
- `maintenance.assets.edit`
- `maintenance.assets.delete`
- `payroll.runs.approve`
- `procurement.orders.submit`

### Standard Actions

| Action | Description |
|--------|-------------|
| `view` / `list` | View/list records |
| `create` | Create new records |
| `edit` / `update` | Modify existing records |
| `delete` | Delete records |
| `restore` | Restore soft-deleted records |
| `approve` | Approve pending items |
| `reject` | Reject pending items |
| `export` | Export data |
| `import` | Import data |

## Adding New Permissions

### Method 1: Using the Permission Seeder

**Location:** `database/seeders/PermissionSeeder.php`

```php
<?php

namespace Database\Seeders;

use App\Domains\Core\Permission\Models\Permission;
use App\Domains\Core\Role\Models\Role;
use Illuminate\Database\Seeder;

class PermissionSeeder extends Seeder
{
    public function run(): void
    {
        // Define permission categories
        $categories = [
            'Accounting' => [
                'assets', 'bank-accounts', 'journals', 'sales', 'purchases',
            ],
            'Construction' => [
                'projects', 'contractors', 'equipment', 'materials',
            ],
            'Procurement' => [
                'drivers', 'orders', 'clients', 'suppliers',
            ],
            'Maintenance' => [
                'assets', 'technicians', 'visits', 'contracts',
            ],
            // Add your new category if needed
            'YourCategory' => [
                'your-resource',
            ],
        ];

        foreach ($categories as $category => $resources) {
            foreach ($resources as $resource) {
                $this->createResourcePermissions($category, $resource);
            }
        }

        // Assign all permissions to super_admin
        $this->assignToSuperAdmin();
    }

    private function createResourcePermissions(string $category, string $resource): void
    {
        $actions = ['view', 'create', 'edit', 'delete', 'restore'];

        foreach ($actions as $action) {
            $permissionName = strtolower($category) . '.' . $resource . '.' . $action;

            Permission::firstOrCreate(
                ['name' => $permissionName, 'guard_name' => 'web'],
                [
                    'guard_name' => 'web',
                    'group_title' => strtolower($category) . '.' . $resource,
                    'category' => $category,
                    'title' => json_encode([
                        'en' => ucfirst($action) . ' ' . str_replace('-', ' ', ucfirst($resource)),
                        'ar' => $this->getArabicTitle($action, $resource),
                    ]),
                ]
            );
        }
    }

    private function assignToSuperAdmin(): void
    {
        $superAdminRole = Role::where('name', 'super_admin')->first();

        if ($superAdminRole) {
            $allPermissions = Permission::all();
            $superAdminRole->syncPermissions($allPermissions);
        }
    }
}
```

### Method 2: Domain-Specific Seeder

Create a separate seeder for your domain:

**Location:** `database/seeders/YourDomainPermissionsSeeder.php`

```php
<?php

namespace Database\Seeders;

use App\Domains\Core\Permission\Models\Permission;
use App\Domains\Core\Role\Models\Role;
use Illuminate\Database\Seeder;

class YourDomainPermissionsSeeder extends Seeder
{
    public function run(): void
    {
        $permissions = [
            // Standard CRUD permissions
            [
                'name' => 'yourdomain.resources.view',
                'title' => ['en' => 'View Resources', 'ar' => 'عرض الموارد'],
                'group_title' => 'yourdomain.resources',
                'category' => 'YourDomain',
            ],
            [
                'name' => 'yourdomain.resources.create',
                'title' => ['en' => 'Create Resources', 'ar' => 'إنشاء الموارد'],
                'group_title' => 'yourdomain.resources',
                'category' => 'YourDomain',
            ],
            [
                'name' => 'yourdomain.resources.edit',
                'title' => ['en' => 'Edit Resources', 'ar' => 'تعديل الموارد'],
                'group_title' => 'yourdomain.resources',
                'category' => 'YourDomain',
            ],
            [
                'name' => 'yourdomain.resources.delete',
                'title' => ['en' => 'Delete Resources', 'ar' => 'حذف الموارد'],
                'group_title' => 'yourdomain.resources',
                'category' => 'YourDomain',
            ],
            // Custom action permissions
            [
                'name' => 'yourdomain.resources.approve',
                'title' => ['en' => 'Approve Resources', 'ar' => 'الموافقة على الموارد'],
                'group_title' => 'yourdomain.resources',
                'category' => 'YourDomain',
            ],
            [
                'name' => 'yourdomain.resources.export',
                'title' => ['en' => 'Export Resources', 'ar' => 'تصدير الموارد'],
                'group_title' => 'yourdomain.resources',
                'category' => 'YourDomain',
            ],
        ];

        foreach ($permissions as $permission) {
            Permission::firstOrCreate(
                ['name' => $permission['name'], 'guard_name' => 'web'],
                [
                    'guard_name' => 'web',
                    'group_title' => $permission['group_title'],
                    'category' => $permission['category'],
                    'title' => json_encode($permission['title']),
                ]
            );
        }

        // Assign to super_admin role
        $superAdmin = Role::where('name', 'super_admin')->first();
        if ($superAdmin) {
            $permissionNames = array_column($permissions, 'name');
            $superAdmin->givePermissionTo($permissionNames);
        }
    }
}
```

### Method 3: Migration-Based Permissions

**Location:** `app/Domains/YourDomain/Database/Migrations/`

```php
<?php

use App\Domains\Core\Permission\Models\Permission;
use App\Domains\Core\Role\Models\Role;
use Illuminate\Database\Migrations\Migration;

return new class extends Migration
{
    private array $permissions = [
        [
            'name' => 'yourdomain.resources.view',
            'title' => ['en' => 'View Resources', 'ar' => 'عرض الموارد'],
            'group_title' => 'yourdomain.resources',
            'category' => 'YourDomain',
        ],
        // ... more permissions
    ];

    public function up(): void
    {
        foreach ($this->permissions as $permission) {
            Permission::firstOrCreate(
                ['name' => $permission['name'], 'guard_name' => 'web'],
                [
                    'guard_name' => 'web',
                    'group_title' => $permission['group_title'],
                    'category' => $permission['category'],
                    'title' => json_encode($permission['title']),
                ]
            );
        }

        // Assign to super_admin
        $superAdmin = Role::where('name', 'super_admin')->first();
        if ($superAdmin) {
            $permissionNames = array_column($this->permissions, 'name');
            $superAdmin->givePermissionTo($permissionNames);
        }

        // Clear permission cache
        app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
    }

    public function down(): void
    {
        $permissionNames = array_column($this->permissions, 'name');
        Permission::whereIn('name', $permissionNames)->delete();

        app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
    }
};
```

## Implementing Permission Checks

### Route Middleware

**Location:** `routes/apis/*.php`

```php
Route::middleware(['auth:sanctum'])->group(function () {
    Route::prefix('your-resources')->group(function () {
        Route::get('/', [YourResourceController::class, 'index'])
            ->name('your-resources.index')
            ->middleware('permission:yourdomain.resources.view');

        Route::post('/', [YourResourceController::class, 'store'])
            ->name('your-resources.store')
            ->middleware('permission:yourdomain.resources.create');

        Route::get('/{id}', [YourResourceController::class, 'show'])
            ->name('your-resources.show')
            ->middleware('permission:yourdomain.resources.view');

        Route::put('/{id}', [YourResourceController::class, 'update'])
            ->name('your-resources.update')
            ->middleware('permission:yourdomain.resources.edit');

        Route::delete('/{id}', [YourResourceController::class, 'destroy'])
            ->name('your-resources.destroy')
            ->middleware('permission:yourdomain.resources.delete');

        // Custom action
        Route::post('/{id}/approve', [YourResourceController::class, 'approve'])
            ->name('your-resources.approve')
            ->middleware('permission:yourdomain.resources.approve');
    });
});
```

### Controller with AddPermissions Trait

**Location:** `app/Core/Traits/AddPermissions.php`

```php
<?php

namespace App\Domains\YourDomain\Http\Controllers\V1;

use App\Core\Http\Controllers\V1\ApiBaseController;
use App\Core\Traits\AddPermissions;

class YourResourceController extends ApiBaseController
{
    use AddPermissions;

    public function __construct()
    {
        // Apply standard CRUD permissions
        $this->applyPermissions(
            'yourdomain.resources',  // Permission prefix
            ['index', 'show', 'store', 'update', 'destroy'],  // CRUD methods
            [
                // Custom methods mapping
                'approve' => 'approve',
                'export' => 'export',
            ]
        );
    }

    // Controller methods...
}
```

### Manual Permission Checks

```php
class YourService
{
    public function performAction(): void
    {
        $user = Auth::user();

        // Check single permission
        if (!$user->hasPermissionTo('yourdomain.resources.approve')) {
            throw new UnauthorizedException('You do not have permission to approve.');
        }

        // Check any of multiple permissions
        if (!$user->hasAnyPermission(['yourdomain.resources.edit', 'yourdomain.resources.approve'])) {
            throw new UnauthorizedException('Insufficient permissions.');
        }

        // Check all permissions
        if (!$user->hasAllPermissions(['yourdomain.resources.view', 'yourdomain.resources.edit'])) {
            throw new UnauthorizedException('All permissions required.');
        }

        // Check role
        if ($user->hasRole('super_admin')) {
            // Super admin bypass
        }
    }
}
```

### Policy-Based Authorization

**Location:** `app/Domains/YourDomain/Policies/YourResourcePolicy.php`

```php
<?php

namespace App\Domains\YourDomain\Policies;

use App\Domains\Core\User\Models\User;
use App\Domains\YourDomain\Models\YourResource;

class YourResourcePolicy
{
    public function viewAny(User $user): bool
    {
        return $user->hasPermissionTo('yourdomain.resources.view');
    }

    public function view(User $user, YourResource $resource): bool
    {
        return $user->hasPermissionTo('yourdomain.resources.view');
    }

    public function create(User $user): bool
    {
        return $user->hasPermissionTo('yourdomain.resources.create');
    }

    public function update(User $user, YourResource $resource): bool
    {
        return $user->hasPermissionTo('yourdomain.resources.edit');
    }

    public function delete(User $user, YourResource $resource): bool
    {
        return $user->hasPermissionTo('yourdomain.resources.delete');
    }

    // Custom authorization with business logic
    public function approve(User $user, YourResource $resource): bool
    {
        // Must have permission AND resource must be in pending status
        return $user->hasPermissionTo('yourdomain.resources.approve')
            && $resource->status === 'pending';
    }

    // Owner-based authorization
    public function updateOwn(User $user, YourResource $resource): bool
    {
        return $resource->created_by === $user->id;
    }
}
```

**Register Policy in AppServiceProvider:**

```php
use Illuminate\Support\Facades\Gate;

public function boot(): void
{
    Gate::policy(YourResource::class, YourResourcePolicy::class);
}
```

**Use in Controller:**

```php
public function update(Request $request, YourResource $resource)
{
    $this->authorize('update', $resource);

    // Proceed with update...
}
```

## Role Management

### Creating Roles

```php
use App\Domains\Core\Role\Models\Role;

// Create a new role
$role = Role::create([
    'name' => Str::random(10),  // System identifier
    'title' => json_encode(['en' => 'Manager', 'ar' => 'مدير']),
    'guard_name' => 'web',
    'section' => 'accounting',  // Optional: accounting or procurement
    'company_id' => company()->id,
]);

// Assign permissions to role
$role->givePermissionTo([
    'yourdomain.resources.view',
    'yourdomain.resources.create',
    'yourdomain.resources.edit',
]);

// Or sync permissions (replaces existing)
$role->syncPermissions($permissionArray);
```

### Assigning Roles to Users

```php
// Assign role to user
$user->assignRole('manager');

// Assign by role instance
$user->assignRole($role);

// Remove role
$user->removeRole('manager');

// Sync roles (replaces existing)
$user->syncRoles(['manager', 'viewer']);
```

## Running Seeders

### Run Permission Seeder

```bash
# Run the main permission seeder
php artisan db:seed --class=PermissionSeeder

# Run domain-specific seeder
php artisan db:seed --class=YourDomainPermissionsSeeder
```

### Clear Permission Cache

After adding permissions, clear the cache:

```php
// In code
app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();

// Via artisan
php artisan permission:cache-reset
```

## Frontend Permission Checks

### Include Permissions in User Resource

```php
class UserResource extends JsonResource
{
    public function toArray($request): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'email' => $this->email,
            'roles' => $this->roles->pluck('name'),
            'permissions' => $this->getAllPermissions()->pluck('name'),
        ];
    }
}
```

### Check Permission in Frontend

```javascript
// Check if user has permission
const canEdit = user.permissions.includes('yourdomain.resources.edit');

// Check multiple permissions
const canManage = ['edit', 'delete', 'approve']
    .some(action => user.permissions.includes(`yourdomain.resources.${action}`));
```

## Best Practices

### DO:

1. **Follow naming convention** - `{domain}.{resource}.{action}`
2. **Create domain-specific seeders** for organized permission management
3. **Always assign to super_admin** after creating permissions
4. **Clear cache** after permission changes
5. **Use policies** for complex authorization logic
6. **Translate permission titles** for multilingual support

### DON'T:

1. **Don't hardcode role checks** - use permission checks instead
2. **Don't skip cache clearing** - leads to stale permissions
3. **Don't create duplicate permissions** - use `firstOrCreate`
4. **Don't forget middleware** on routes
5. **Don't mix authorization logic** - keep it in policies

## Troubleshooting

### Permission not working

1. Clear permission cache:
   ```php
   app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
   ```

2. Verify permission exists:
   ```php
   Permission::where('name', 'yourdomain.resources.view')->exists();
   ```

3. Check user has permission:
   ```php
   Auth::user()->hasPermissionTo('yourdomain.resources.view');
   ```

### Role not assigned

1. Check role exists and has correct guard:
   ```php
   Role::where('name', 'role-name')->where('guard_name', 'web')->first();
   ```

2. Verify user-role assignment:
   ```php
   Auth::user()->roles()->pluck('name');
   ```

### Migration conflicts

If permissions already exist, use `firstOrCreate` to avoid duplicates:

```php
Permission::firstOrCreate(
    ['name' => $permissionName, 'guard_name' => 'web'],
    [...attributes...]
);
```
