Skip to content

অধ্যায় ৮: প্যাকেজ মিডলওয়্যার এবং পলিসি আর্কিটেকচার

৮.১ HTTP কার্নেল এবং মিডলওয়্যার পাইপলাইন: ইন্টারনালস

লারাভেলে একটি রিকোয়েস্ট যখন প্রবেশ করে, এটি একটি "Onion Architecture" বা পেঁয়াজের স্তরের মতো অনেকগুলো লেয়ার ভেদ করে অ্যাপ্লিকেশনে ঢোকে। এই লেয়ারগুলোই হলো মিডলওয়্যার

৮.১.১ Illuminate\Pipeline\Pipeline মেকানিজম

লারাভেলের Kernel ক্লাসটি মূলত Pipeline ক্লাস ব্যবহার করে। এর মূল লজিকটি অনেকটা এমন:

php
// লারাভেলের ইন্টারনাল সুডোকোড (Pseudo-code)
return (new Pipeline($container))
    ->send($request)
    ->through($middlewareList)
    ->then(function ($request) {
        // সব মিডলওয়্যার পার হওয়ার পর এখানে কন্ট্রোলার এক্সিকিউট হয়
        return $controller->action();
    });

প্যাকেজ ডেভেলপার হিসেবে আমাদের চ্যালেঞ্জ হলো—ইউজারের Kernel.php এডিট না করেই এই $middlewareList-এ আমাদের নিজস্ব ক্লাস ঢুকিয়ে দেওয়া।

৮.২ প্যাকেজ স্পেসিফিক মিডলওয়্যার তৈরি ও রেজিস্ট্রেশন

ধরি, আমাদের প্যাকেজে একটি অ্যাডমিন প্যানেল আছে এবং আমরা চাই শুধুমাত্র নির্দিষ্ট আইপি এড্রেস বা নির্দিষ্ট ইমেইলের ইউজাররা এটি এক্সেস করতে পারুক।

৮.২.১ মিডলওয়্যার ক্লাস

ফাইল: src/Http/Middleware/EnsureInvoiceAdmin.php

php
namespace DevMaster\InvoiceLite\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class EnsureInvoiceAdmin
{
    public function handle(Request $request, Closure $next)
    {
        // লজিক: কনফিগারেশন থেকে অ্যাডমিন ইমেইল চেক করা
        $admins = config('invoicelite.admins', []);
        
        if (! $request->user() || ! in_array($request->user()->email, $admins)) {
            abort(403, 'Unauthorized access to InvoiceLite Dashboard.');
        }

        return $next($request);
    }
}

৮.২.২ রানটাইম রেজিস্ট্রেশন (The Router Injection)

যেহেতু আমরা Kernel.php এডিট করতে পারব না, তাই আমাদের ServiceProvider-এর boot মেথড ব্যবহার করে লারাভেলের রাউটারকে আমাদের মিডলওয়্যার সম্পর্কে জানাতে হবে।

ফাইল: src/InvoiceLiteServiceProvider.php

php
use Illuminate\Routing\Router;

public function boot(): void
{
    /** @var Router $router */
    $router = $this->app->make(Router::class);

    // ১. এলিয়াস তৈরি করা (যাতে রাউটে নাম ধরে ডাকা যায়)
    $router->aliasMiddleware('invoice.admin', \DevMaster\InvoiceLite\Http\Middleware\EnsureInvoiceAdmin::class);

. (অপশনাল) গ্লোবাল ওয়েব গ্রুপে পুশ করা
     $router->pushMiddlewareToGroup('web', \DevMaster\InvoiceLite\Http\Middleware\TraceUserActivity::class);
}

এখন আমরা রাউট ফাইলে middleware('invoice.admin') ব্যবহার করতে পারব।

৮.৩ পলিসি (Policy) এবং গেট (Gate) আর্কিটেকচার

মিডলওয়্যার পুরো রাউটের এক্সেস নিয়ন্ত্রণ করে। কিন্তু আপনি যদি চান—"ইউজার ইনভয়েস দেখতে পারবে, কিন্তু ডিলিট করতে পারবে না"—তবে আপনার Policy দরকার।

প্যাকেজের পলিসি রেজিস্টার করার নিয়ম সাধারণ অ্যাপ থেকে আলাদা।

৮.৩.১ পলিসি ক্লাস তৈরি

ফাইল: src/Policies/InvoicePolicy.php

php
namespace DevMaster\InvoiceLite\Policies;

use App\Models\User; // হোস্ট অ্যাপের ইউজার মডেল
use DevMaster\InvoiceLite\Models\Invoice;
use Illuminate\Auth\Access\HandlesAuthorization;

class InvoicePolicy
{
    use HandlesAuthorization;

    public function view(User $user, Invoice $invoice)
    {
        // ইনভয়েসটি কি এই ইউজারের?
        return $user->id === $invoice->user_id;
    }

    public function delete(User $user, Invoice $invoice)
    {
        // শুধুমাত্র ড্রাফট ইনভয়েস ডিলিট করা যাবে
        return $user->id === $invoice->user_id && $invoice->status === 'draft';
    }
}

৮.৩.২ প্যাকেজে পলিসি রেজিস্ট্রেশন

সাধারণ অ্যাপে AuthServiceProvider-এ $policies অ্যারে থাকে। প্যাকেজে আমাদের ServiceProvider এর boot মেথডে Gate ফ্যাসাড ব্যবহার করতে হবে।

php
use Illuminate\Support\Facades\Gate;
use DevMaster\InvoiceLite\Models\Invoice;
use DevMaster\InvoiceLite\Policies\InvoicePolicy;

public function boot(): void
{
    // ম্যানুয়াল রেজিস্ট্রেশন
    Gate::policy(Invoice::class, InvoicePolicy::class);
}

আর্কিটেক্ট নোট: Gate::guessPolicyNamesUsing() ব্যবহার করে অটো-ডিসকভারি চালু করা যায় (যেমন আমরা ফ্যাক্টরির ক্ষেত্রে করেছিলাম), তবে প্যাকেজের ক্ষেত্রে এক্সপ্লিসিটলি বা স্পষ্টভাবে ডিফাইন করা নিরাপদ।

৮.৪ ইন্টারনালস: authorize() মেথড কিভাবে কাজ করে?

কন্ট্রোলারে যখন আমরা $this->authorize('delete', $invoice) লিখি, তখন ভেতরে কী ঘটে?

  1. Controller Trait: BaseController-এ AuthorizesRequests ট্রেইট থাকে। এটি authorize মেথড প্রোভাইড করে
  2. Gate Facade Call: এটি Gate::authorize('delete', [$invoice]) কল করে
  3. Policy Resolution: Gate ক্লাস চেক করে Invoice ক্লাসের জন্য কোনো পলিসি রেজিস্টার করা আছে কি না। (আমরা ৮.৩.২-এ যা করেছি)
  4. Method Invocation: এটি InvoicePolicy এর delete মেথড কল করে এবং বর্তমান অথেনটিকেটেড ইউজারকে প্রথম আর্গুমেন্ট হিসেবে পাস করে
  5. Decision: যদি পলিসি true রিটার্ন করে, কোড সামনে আগায়। false রিটার্ন করলে AuthorizationException থ্রো করে, যা লারাভেল অটোমেটিক্যালি ৪০৩ Forbidden রেসপন্সে রূপান্তর করে

৮.৫ ডাইনামিক পারমিশন সিস্টেম (কনফিগারেশন বেসড)

প্যাকেজ হিসেবে আমরা ইউজারের ওপর জোর করে আমাদের পলিসি চাপিয়ে দিতে পারি না। ইউজার হয়তো চায় তার নিজস্ব পারমিশন প্যাকেজ (যেমন Spatie Permission) ব্যবহার করতে।

এর জন্য আমরা একটি "Gate Hook" তৈরি করতে পারি।

Service Class:

php
class InvoiceManager
{
    public function authorize($ability, $arguments = [])
    {
        // ১. ইউজার যদি কনফিগারেশনে কাস্টম গেট ডিফাইন করে থাকে
        if (config('invoicelite.use_custom_auth')) {
             return Gate::authorize("invoicelite.{$ability}", $arguments);
        }

        // ২. নাহলে আমাদের ডিফল্ট পলিসি
        return Gate::authorize($ability, $arguments);
    }
}

এটি ইউজারকে সর্বোচ্চ ফ্লেক্সিবিলিটি দেয়।

🔨 ৮.৬ Practical Implementation: Middleware & Authorization for InvoiceBuilder

এখন আমরা আমাদের InvoiceBuilder package এ comprehensive security layer যোগ করব।

৮.৬.১ Enhanced Security Configuration

প্রথমে Chapter 5 এর configuration এ security options যোগ করি:

php
// packages/DevMaster/InvoiceBuilder/config/invoice-builder.php এ security section যোগ করুন

/*
|--------------------------------------------------------------------------
| Security & Authorization
|--------------------------------------------------------------------------
*/
'security' => [
    // Enable/disable security features
    'enabled' => env('INVOICE_SECURITY_ENABLED', true),
    
    // Admin access configuration
    'admin' => [
        'enabled' => env('INVOICE_ADMIN_ENABLED', true),
        'emails' => env('INVOICE_ADMIN_EMAILS', []), // Comma-separated in .env
        'roles' => env('INVOICE_ADMIN_ROLES', ['admin', 'super-admin']),
        'permissions' => env('INVOICE_ADMIN_PERMISSIONS', ['invoice.admin']),
        'ip_whitelist' => env('INVOICE_ADMIN_IP_WHITELIST', []),
    ],
    
    // API security
    'api' => [
        'rate_limit' => env('INVOICE_API_RATE_LIMIT', '60,1'), // requests,minutes
        'require_token' => env('INVOICE_API_REQUIRE_TOKEN', true),
        'allowed_origins' => env('INVOICE_API_CORS_ORIGINS', '*'),
    ],
    
    // Authorization strategy
    'authorization' => [
        'strategy' => env('INVOICE_AUTH_STRATEGY', 'policy'), // policy|gate|custom|disabled
        'custom_gate_prefix' => env('INVOICE_CUSTOM_GATE_PREFIX', 'invoice-builder'),
        'owner_field' => env('INVOICE_OWNER_FIELD', 'billable_id'), // for polymorphic
        'use_teams' => env('INVOICE_USE_TEAMS', false),
    ],
    
    // Audit logging
    'audit' => [
        'enabled' => env('INVOICE_AUDIT_ENABLED', true),
        'events' => ['created', 'updated', 'deleted', 'viewed', 'downloaded'],
        'store_ip' => env('INVOICE_AUDIT_STORE_IP', true),
        'store_user_agent' => env('INVOICE_AUDIT_STORE_USER_AGENT', false),
    ],
],

৮.৬.২ Middleware Classes

Admin Access Middleware:

php
// packages/DevMaster/InvoiceBuilder/src/Http/Middleware/EnsureInvoiceAdmin.php
<?php

namespace DevMaster\InvoiceBuilder\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use DevMaster\InvoiceBuilder\Services\ConfigManager;
use Symfony\Component\HttpFoundation\Response;

class EnsureInvoiceAdmin
{
    protected ConfigManager $config;

    public function __construct(ConfigManager $config)
    {
        $this->config = $config;
    }

    /**
     * Handle an incoming request.
     */
    public function handle(Request $request, Closure $next): Response
    {
        // Check if admin feature is enabled
        if (!$this->config->get('security.admin.enabled', true)) {
            return $next($request);
        }

        // Check user authentication
        if (!$request->user()) {
            return $this->unauthorized($request, 'Authentication required');
        }

        $user = $request->user();

        // Check admin access
        if (!$this->isAuthorizedAdmin($user, $request)) {
            return $this->unauthorized($request, 'Admin access required');
        }

        return $next($request);
    }

    /**
     * Check if user is authorized admin
     */
    protected function isAuthorizedAdmin($user, Request $request): bool
    {
        // Check by email
        $adminEmails = $this->config->get('security.admin.emails', []);
        if (!empty($adminEmails) && in_array($user->email, $adminEmails)) {
            return true;
        }

        // Check by roles (if user model has roles)
        $adminRoles = $this->config->get('security.admin.roles', []);
        if (!empty($adminRoles) && method_exists($user, 'hasRole')) {
            foreach ($adminRoles as $role) {
                if ($user->hasRole($role)) {
                    return true;
                }
            }
        }

        // Check by permissions (if using permission package)
        $adminPermissions = $this->config->get('security.admin.permissions', []);
        if (!empty($adminPermissions) && method_exists($user, 'can')) {
            foreach ($adminPermissions as $permission) {
                if ($user->can($permission)) {
                    return true;
                }
            }
        }

        // Check IP whitelist
        $ipWhitelist = $this->config->get('security.admin.ip_whitelist', []);
        if (!empty($ipWhitelist) && in_array($request->ip(), $ipWhitelist)) {
            return true;
        }

        return false;
    }

    /**
     * Return unauthorized response
     */
    protected function unauthorized(Request $request, string $message): Response
    {
        if ($request->expectsJson()) {
            return response()->json([
                'success' => false,
                'message' => $message,
                'error' => 'UNAUTHORIZED_ACCESS'
            ], 403);
        }

        abort(403, $message);
    }
}

Invoice Owner Middleware:

php
// packages/DevMaster/InvoiceBuilder/src/Http/Middleware/EnsureInvoiceOwner.php
<?php

namespace DevMaster\InvoiceBuilder\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use DevMaster\InvoiceBuilder\Models\Invoice;
use DevMaster\InvoiceBuilder\Services\ConfigManager;
use Symfony\Component\HttpFoundation\Response;

class EnsureInvoiceOwner
{
    protected ConfigManager $config;

    public function __construct(ConfigManager $config)
    {
        $this->config = $config;
    }

    /**
     * Handle an incoming request.
     */
    public function handle(Request $request, Closure $next): Response
    {
        // Get invoice from route parameter
        $invoiceId = $request->route('id') ?? $request->route('invoice');
        
        if (!$invoiceId) {
            return $next($request); // No invoice to check
        }

        $invoice = Invoice::find($invoiceId);
        
        if (!$invoice) {
            return $this->notFound($request);
        }

        // Check ownership
        if (!$this->isOwner($request->user(), $invoice)) {
            return $this->unauthorized($request, 'You do not have access to this invoice');
        }

        // Add invoice to request for controller use
        $request->attributes->set('invoice', $invoice);

        return $next($request);
    }

    /**
     * Check if user owns the invoice
     */
    protected function isOwner($user, Invoice $invoice): bool
    {
        if (!$user) {
            return false;
        }

        $usePolymorphic = $this->config->get('database.models.polymorphic', true);

        if ($usePolymorphic) {
            // Check polymorphic relationship
            return $invoice->billable_type === get_class($user) 
                && $invoice->billable_id === $user->id;
        } else {
            // Check direct user relationship
            return $invoice->user_id === $user->id;
        }
    }

    /**
     * Return not found response
     */
    protected function notFound(Request $request): Response
    {
        if ($request->expectsJson()) {
            return response()->json([
                'success' => false,
                'message' => 'Invoice not found',
                'error' => 'INVOICE_NOT_FOUND'
            ], 404);
        }

        abort(404, 'Invoice not found');
    }

    /**
     * Return unauthorized response
     */
    protected function unauthorized(Request $request, string $message): Response
    {
        if ($request->expectsJson()) {
            return response()->json([
                'success' => false,
                'message' => $message,
                'error' => 'ACCESS_DENIED'
            ], 403);
        }

        abort(403, $message);
    }
}

Rate Limiting Middleware:

php
// packages/DevMaster/InvoiceBuilder/src/Http/Middleware/InvoiceRateLimit.php
<?php

namespace DevMaster\InvoiceBuilder\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Cache\RateLimiter;
use DevMaster\InvoiceBuilder\Services\ConfigManager;
use Symfony\Component\HttpFoundation\Response;

class InvoiceRateLimit
{
    protected RateLimiter $limiter;
    protected ConfigManager $config;

    public function __construct(RateLimiter $limiter, ConfigManager $config)
    {
        $this->limiter = $limiter;
        $this->config = $config;
    }

    /**
     * Handle an incoming request.
     */
    public function handle(Request $request, Closure $next): Response
    {
        $rateLimitConfig = $this->config->get('security.api.rate_limit', '60,1');
        [$maxAttempts, $decayMinutes] = explode(',', $rateLimitConfig);

        $key = $this->resolveRequestSignature($request);

        if ($this->limiter->tooManyAttempts($key, $maxAttempts)) {
            return $this->buildTooManyAttemptsResponse($request, $key, $maxAttempts);
        }

        $this->limiter->hit($key, $decayMinutes * 60);

        $response = $next($request);

        return $this->addHeaders(
            $response,
            $maxAttempts,
            $this->calculateRemainingAttempts($key, $maxAttempts)
        );
    }

    /**
     * Resolve request signature.
     */
    protected function resolveRequestSignature(Request $request): string
    {
        $userId = $request->user()?->id ?? 'guest';
        return sha1('invoice-api:' . $userId . '|' . $request->ip());
    }

    /**
     * Create a 'too many attempts' response.
     */
    protected function buildTooManyAttemptsResponse(Request $request, string $key, int $maxAttempts): Response
    {
        $retryAfter = $this->limiter->availableIn($key);

        if ($request->expectsJson()) {
            return response()->json([
                'success' => false,
                'message' => 'Too many requests',
                'error' => 'RATE_LIMIT_EXCEEDED',
                'retry_after' => $retryAfter
            ], 429);
        }

        return response('Too Many Requests', 429)
            ->header('Retry-After', $retryAfter);
    }

    /**
     * Add rate limit headers to response.
     */
    protected function addHeaders(Response $response, int $maxAttempts, int $remainingAttempts): Response
    {
        return $response->withHeaders([
            'X-RateLimit-Limit' => $maxAttempts,
            'X-RateLimit-Remaining' => $remainingAttempts,
        ]);
    }

    /**
     * Calculate remaining attempts.
     */
    protected function calculateRemainingAttempts(string $key, int $maxAttempts): int
    {
        return max(0, $maxAttempts - $this->limiter->attempts($key));
    }
}

৮.৆.৩ Policy Classes

Invoice Policy:

php
// packages/DevMaster/InvoiceBuilder/src/Policies/InvoicePolicy.php
<?php

namespace DevMaster\InvoiceBuilder\Policies;

use Illuminate\Auth\Access\HandlesAuthorization;
use DevMaster\InvoiceBuilder\Models\Invoice;
use DevMaster\InvoiceBuilder\Services\ConfigManager;

class InvoicePolicy
{
    use HandlesAuthorization;

    protected ConfigManager $config;

    public function __construct(ConfigManager $config)
    {
        $this->config = $config;
    }

    /**
     * Determine whether the user can view any invoices.
     */
    public function viewAny($user): bool
    {
        return $this->checkCustomGate('viewAny', $user) ?? true;
    }

    /**
     * Determine whether the user can view the invoice.
     */
    public function view($user, Invoice $invoice): bool
    {
        return $this->checkCustomGate('view', $user, $invoice) 
            ?? $this->isOwner($user, $invoice);
    }

    /**
     * Determine whether the user can create invoices.
     */
    public function create($user): bool
    {
        return $this->checkCustomGate('create', $user) ?? true;
    }

    /**
     * Determine whether the user can update the invoice.
     */
    public function update($user, Invoice $invoice): bool
    {
        return $this->checkCustomGate('update', $user, $invoice) 
            ?? ($this->isOwner($user, $invoice) && $invoice->isEditable());
    }

    /**
     * Determine whether the user can delete the invoice.
     */
    public function delete($user, Invoice $invoice): bool
    {
        return $this->checkCustomGate('delete', $user, $invoice) 
            ?? ($this->isOwner($user, $invoice) && $invoice->isDeletable());
    }

    /**
     * Determine whether the user can download the invoice.
     */
    public function download($user, Invoice $invoice): bool
    {
        return $this->checkCustomGate('download', $user, $invoice) 
            ?? $this->isOwner($user, $invoice);
    }

    /**
     * Determine whether the user can send the invoice.
     */
    public function send($user, Invoice $invoice): bool
    {
        return $this->checkCustomGate('send', $user, $invoice) 
            ?? ($this->isOwner($user, $invoice) && $invoice->status !== 'cancelled');
    }

    /**
     * Determine whether the user can mark invoice as paid.
     */
    public function markAsPaid($user, Invoice $invoice): bool
    {
        return $this->checkCustomGate('markAsPaid', $user, $invoice) 
            ?? ($this->isOwner($user, $invoice) && $invoice->status !== 'paid');
    }

    /**
     * Check if user is owner of invoice
     */
    protected function isOwner($user, Invoice $invoice): bool
    {
        if (!$user) {
            return false;
        }

        $usePolymorphic = $this->config->get('database.models.polymorphic', true);

        if ($usePolymorphic) {
            return $invoice->billable_type === get_class($user) 
                && $invoice->billable_id === $user->id;
        } else {
            return $invoice->user_id === $user->id;
        }
    }

    /**
     * Check custom gate if configured
     */
    protected function checkCustomGate(string $ability, $user, ...$arguments): ?bool
    {
        $strategy = $this->config->get('security.authorization.strategy', 'policy');
        
        if ($strategy === 'custom') {
            $prefix = $this->config->get('security.authorization.custom_gate_prefix', 'invoice-builder');
            $gateName = "{$prefix}.{$ability}";
            
            if (\Illuminate\Support\Facades\Gate::has($gateName)) {
                return \Illuminate\Support\Facades\Gate::allows($gateName, $arguments);
            }
        }

        return null; // Use default policy logic
    }
}

Customer Policy:

php
// packages/DevMaster/InvoiceBuilder/src/Policies/CustomerPolicy.php
<?php

namespace DevMaster\InvoiceBuilder\Policies;

use Illuminate\Auth\Access\HandlesAuthorization;
use DevMaster\InvoiceBuilder\Models\Customer;
use DevMaster\InvoiceBuilder\Services\ConfigManager;

class CustomerPolicy
{
    use HandlesAuthorization;

    protected ConfigManager $config;

    public function __construct(ConfigManager $config)
    {
        $this->config = $config;
    }

    /**
     * Determine whether the user can view any customers.
     */
    public function viewAny($user): bool
    {
        return true; // All authenticated users can view customers
    }

    /**
     * Determine whether the user can view the customer.
     */
    public function view($user, Customer $customer): bool
    {
        return true; // All authenticated users can view customers
    }

    /**
     * Determine whether the user can create customers.
     */
    public function create($user): bool
    {
        return true;
    }

    /**
     * Determine whether the user can update the customer.
     */
    public function update($user, Customer $customer): bool
    {
        return true;
    }

    /**
     * Determine whether the user can delete the customer.
     */
    public function delete($user, Customer $customer): bool
    {
        // Can't delete if customer has invoices
        return $customer->invoices()->count() === 0;
    }
}

৮.৆.ৄ Authorization Service

Authorization Manager:

php
// packages/DevMaster/InvoiceBuilder/src/Services/AuthorizationManager.php
<?php

namespace DevMaster\InvoiceBuilder\Services;

use Illuminate\Support\Facades\Gate;
use DevMaster\InvoiceBuilder\Models\Invoice;

class AuthorizationManager
{
    protected ConfigManager $config;

    public function __construct(ConfigManager $config)
    {
        $this->config = $config;
    }

    /**
     * Authorize an action on a resource
     */
    public function authorize(string $ability, $resource = null): bool
    {
        $strategy = $this->config->get('security.authorization.strategy', 'policy');

        if ($strategy === 'disabled') {
            return true;
        }

        return Gate::allows($ability, $resource);
    }

    /**
     * Check if user can perform ability
     */
    public function can(string $ability, $resource = null): bool
    {
        try {
            return $this->authorize($ability, $resource);
        } catch (\Exception $e) {
            return false;
        }
    }

    /**
     * Authorize or fail
     */
    public function authorizeOrFail(string $ability, $resource = null): void
    {
        Gate::authorize($ability, $resource);
    }

    /**
     * Get allowed actions for a resource
     */
    public function getAllowedActions($resource): array
    {
        $actions = ['view', 'update', 'delete'];
        $allowed = [];

        foreach ($actions as $action) {
            if ($this->can($action, $resource)) {
                $allowed[] = $action;
            }
        }

        return $allowed;
    }

    /**
     * Check if user is admin
     */
    public function isAdmin($user = null): bool
    {
        $user = $user ?? auth()->user();
        
        if (!$user) {
            return false;
        }

        $adminEmails = $this->config->get('security.admin.emails', []);
        if (in_array($user->email, $adminEmails)) {
            return true;
        }

        $adminRoles = $this->config->get('security.admin.roles', []);
        if (method_exists($user, 'hasRole')) {
            foreach ($adminRoles as $role) {
                if ($user->hasRole($role)) {
                    return true;
                }
            }
        }

        return false;
    }
}

৮.৆.৫ Enhanced Service Provider with Security Registration

php
// packages/DevMaster/InvoiceBuilder/src/InvoiceBuilderServiceProvider.php এ boot() method update করুন

use Illuminate\Routing\Router;
use Illuminate\Support\Facades\Gate;
use DevMaster\InvoiceBuilder\Models\Invoice;
use DevMaster\InvoiceBuilder\Models\Customer;
use DevMaster\InvoiceBuilder\Policies\InvoicePolicy;
use DevMaster\InvoiceBuilder\Policies\CustomerPolicy;

/**
 * Bootstrap any application services.
 */
public function boot()
{
    // Publishing assets
    if ($this->app->runningInConsole()) {
        $this->publishAssets();
        $this->publishMigrations();
    }

    // Load package resources
    $this->loadPackageResources();
    
    // Register additional services
    $this->registerAdditionalServices();
    
    // Register security features
    $this->registerSecurityFeatures();
    
    // Register factory guessing
    $this->registerFactories();
}

/**
 * Register security features
 */
protected function registerSecurityFeatures()
{
    $this->registerMiddleware();
    $this->registerPolicies();
    $this->registerGates();
}

/**
 * Register middleware
 */
protected function registerMiddleware()
{
    /** @var Router $router */
    $router = $this->app->make(Router::class);
    
    // Register middleware aliases
    $router->aliasMiddleware(
        'invoice.admin', 
        \DevMaster\InvoiceBuilder\Http\Middleware\EnsureInvoiceAdmin::class
    );
    
    $router->aliasMiddleware(
        'invoice.owner', 
        \DevMaster\InvoiceBuilder\Http\Middleware\EnsureInvoiceOwner::class
    );
    
    $router->aliasMiddleware(
        'invoice.rate-limit', 
        \DevMaster\InvoiceBuilder\Http\Middleware\InvoiceRateLimit::class
    );

    // Add to middleware groups if needed
    if (config('invoice-builder.security.enabled', true)) {
        // Add global middleware for API routes
        $router->pushMiddlewareToGroup('api', 
            \DevMaster\InvoiceBuilder\Http\Middleware\InvoiceRateLimit::class
        );
    }
}

/**
 * Register policies
 */
protected function registerPolicies()
{
    $strategy = config('invoice-builder.security.authorization.strategy', 'policy');
    
    if ($strategy === 'policy') {
        Gate::policy(Invoice::class, InvoicePolicy::class);
        Gate::policy(Customer::class, CustomerPolicy::class);
    }
}

/**
 * Register custom gates
 */
protected function registerGates()
{
    $strategy = config('invoice-builder.security.authorization.strategy', 'policy');
    
    if ($strategy === 'gate') {
        $this->registerInvoiceGates();
    }
}

/**
 * Register invoice gates
 */
protected function registerInvoiceGates()
{
    Gate::define('invoice.viewAny', function ($user) {
        return true;
    });

    Gate::define('invoice.view', function ($user, Invoice $invoice) {
        return $this->app->make(InvoicePolicy::class)->view($user, $invoice);
    });

    Gate::define('invoice.create', function ($user) {
        return true;
    });

    Gate::define('invoice.update', function ($user, Invoice $invoice) {
        return $this->app->make(InvoicePolicy::class)->update($user, $invoice);
    });

    Gate::define('invoice.delete', function ($user, Invoice $invoice) {
        return $this->app->make(InvoicePolicy::class)->delete($user, $invoice);
    });
}

/**
 * Register additional services with authorization
 */
protected function registerAdditionalServices()
{
    parent::registerAdditionalServices();

    // Register authorization manager
    $this->app->singleton(
        \DevMaster\InvoiceBuilder\Services\AuthorizationManager::class,
        function ($app) {
            return new \DevMaster\InvoiceBuilder\Services\AuthorizationManager(
                $app->make(\DevMaster\InvoiceBuilder\Services\ConfigManager::class)
            );
        }
    );
}

৮.৆.৆ Enhanced Controllers with Authorization

php
// packages/DevMaster/InvoiceBuilder/src/Http/Controllers/InvoiceController.php এ authorization যোগ করুন

use DevMaster\InvoiceBuilder\Services\AuthorizationManager;

class InvoiceController extends BaseController
{
    protected AuthorizationManager $auth;

    public function __construct(
        \DevMaster\InvoiceBuilder\Services\InvoiceService $invoiceService,
        \DevMaster\InvoiceBuilder\Services\ConfigManager $config,
        \DevMaster\InvoiceBuilder\Services\PdfGenerator $pdfGenerator,
        AuthorizationManager $auth
    ) {
        parent::__construct($invoiceService, $config);
        $this->pdfGenerator = $pdfGenerator;
        $this->auth = $auth;
        
        // Apply owner middleware to specific routes
        $this->middleware('invoice.owner')->only(['show', 'edit', 'update', 'destroy', 'download']);
    }

    /**
     * Display a listing of invoices (with authorization)
     */
    public function index(Request $request)
    {
        $this->auth->authorizeOrFail('viewAny', Invoice::class);

        // Rest of the method...
        $perPage = $this->config->get('views.pagination', 15);
        // ... existing code
    }

    /**
     * Show the form for creating a new invoice
     */
    public function create()
    {
        $this->auth->authorizeOrFail('create', Invoice::class);

        // ... existing code
    }

    /**
     * Store a newly created invoice (with authorization)
     */
    public function store(InvoiceStoreRequest $request)
    {
        $this->auth->authorizeOrFail('create', Invoice::class);

        // ... existing code
    }

    /**
     * Display the specified invoice (with authorization)
     */
    public function show(Request $request, $id)
    {
        // Get invoice from middleware
        $invoice = $request->attributes->get('invoice');
        $this->auth->authorizeOrFail('view', $invoice);

        // Get allowed actions for view
        $allowedActions = $this->auth->getAllowedActions($invoice);

        if ($request->expectsJson()) {
            return $this->jsonResponse([
                'invoice' => $invoice,
                'allowed_actions' => $allowedActions,
            ]);
        }

        return $this->view('invoices.show', [
            'invoice' => $invoice->toArray(),
            'allowed_actions' => $allowedActions,
        ]);
    }

    /**
     * Update the specified invoice (with authorization)
     */
    public function update(InvoiceUpdateRequest $request, $id)
    {
        $invoice = $request->attributes->get('invoice');
        $this->auth->authorizeOrFail('update', $invoice);

        // ... existing code
    }

    /**
     * Remove the specified invoice (with authorization)
     */
    public function destroy(Request $request, $id)
    {
        $invoice = $request->attributes->get('invoice');
        $this->auth->authorizeOrFail('delete', $invoice);

        // ... existing code
    }

    /**
     * Download invoice as PDF (with authorization)
     */
    public function download(Request $request, $id)
    {
        $invoice = $request->attributes->get('invoice');
        $this->auth->authorizeOrFail('download', $invoice);

        // ... existing PDF generation code
    }

    /**
     * Mark invoice as paid (new method)
     */
    public function markAsPaid(Request $request, $id)
    {
        $invoice = $request->attributes->get('invoice');
        $this->auth->authorizeOrFail('markAsPaid', $invoice);

        $invoice->markAsPaid();

        if ($request->expectsJson()) {
            return $this->jsonResponse($invoice, 'Invoice marked as paid successfully');
        }

        return redirect()
            ->route($this->config->get('routes.name_prefix') . '.show', ['id' => $id])
            ->with('success', 'Invoice marked as paid successfully');
    }
}

৮.৆.৭ Enhanced Routes with Middleware

php
// packages/DevMaster/InvoiceBuilder/routes/web.php এ middleware যোগ করুন

Route::group($routeGroup, function () {
    // Public routes (with basic auth)
    Route::middleware('auth')->group(function () {
        Route::get('/', [InvoiceController::class, 'index'])->name('index');
        Route::get('/create', [InvoiceController::class, 'create'])->name('create');
        Route::post('/', [InvoiceController::class, 'store'])->name('store');
    });
    
    // Owner-protected routes
    Route::middleware(['auth', 'invoice.owner'])->group(function () {
        Route::get('/{id}', [InvoiceController::class, 'show'])->name('show');
        Route::get('/{id}/edit', [InvoiceController::class, 'edit'])->name('edit');
        Route::put('/{id}', [InvoiceController::class, 'update'])->name('update');
        Route::delete('/{id}', [InvoiceController::class, 'destroy'])->name('destroy');
        Route::get('/{id}/download', [InvoiceController::class, 'download'])->name('download');
        Route::patch('/{id}/mark-as-paid', [InvoiceController::class, 'markAsPaid'])->name('mark-as-paid');
    });

    // Admin routes
    Route::middleware(['auth', 'invoice.admin'])->prefix('admin')->name('admin.')->group(function () {
        Route::get('/dashboard', [AdminController::class, 'dashboard'])->name('dashboard');
        Route::get('/invoices', [AdminController::class, 'invoices'])->name('invoices');
        Route::get('/settings', [AdminController::class, 'settings'])->name('settings');
    });
});

// API routes with rate limiting
if (config('invoice-builder.api.enabled', false)) {
    Route::group([
        'prefix' => 'api/' . config('invoice-builder.routes.prefix', 'invoices'),
        'as' => config('invoice-builder.routes.name_prefix', 'invoice-builder') . '.api.',
        'middleware' => ['api', 'invoice.rate-limit'],
    ], function () {
        Route::middleware('auth:sanctum')->group(function () {
            Route::apiResource('invoices', InvoiceController::class);
        });
    });
}

৮.৆.৮ Testing Security Features

Security Configuration Testing:

bash
# Update config
php artisan vendor:publish --tag=invoice-builder-config

# Add to .env
INVOICE_SECURITY_ENABLED=true
INVOICE_ADMIN_EMAILS="admin@example.com,super@example.com"
INVOICE_ADMIN_ROLES="admin,super-admin"
INVOICE_AUTH_STRATEGY=policy

Middleware Testing:

php
php artisan tinker

>>> // Test admin middleware
>>> $config = app(DevMaster\InvoiceBuilder\Services\ConfigManager::class);
>>> $middleware = app(DevMaster\InvoiceBuilder\Http\Middleware\EnsureInvoiceAdmin::class);
>>> 
>>> // Test authorization manager
>>> $auth = app(DevMaster\InvoiceBuilder\Services\AuthorizationManager::class);
>>> $auth->isAdmin(); // Test with current user
>>> 
>>> // Test policy
>>> $invoice = DevMaster\InvoiceBuilder\Models\Invoice::first();
>>> Gate::allows('view', $invoice);
>>> Gate::allows('delete', $invoice);

Route Protection Testing:

bash
# Test protected routes
curl -X GET http://localhost:8000/invoices/1 # Should require auth
curl -X DELETE http://localhost:8000/invoices/1 # Should require ownership

৮.৯ Chapter 8 Checkpoint

এই chapter শেষে আপনার InvoiceBuilder package এ আছে:

  • Complete Security System - Authentication, authorization, and access control
  • Flexible Middleware - Admin access, owner verification, rate limiting
  • Policy-based Authorization - Granular permissions for all invoice operations
  • Configurable Security - Multiple auth strategies and customizable rules
  • Admin Protection - Email, role, permission, and IP-based admin access
  • API Security - Rate limiting and token-based authentication
  • Route Protection - Middleware-protected routes with proper error handling
  • Authorization Service - Centralized permission management

Test Commands Summary:

bash
# Security configuration
INVOICE_ADMIN_EMAILS="admin@test.com" php artisan serve

# Middleware testing
php artisan route:list --middleware=invoice.admin

# Authorization testing
php artisan tinker
>>> Gate::allows('view', $invoice)
>>> app('DevMaster\InvoiceBuilder\Services\AuthorizationManager')->isAdmin()

Next Chapter: Chapter 9 এ আমরা Console Commands, Artisan Commands এবং automation features যোগ করব।

Key Concepts Mastered:

  • Middleware registration and pipeline integration
  • Policy-based authorization with flexible strategies
  • Rate limiting and API security
  • Admin access control with multiple verification methods
  • Configurable security architecture
  • Route protection with proper error handling

সৎ ক্রেডিট: লেখক AI, সম্পাদক আবুল হাসান