Enterprise الإصدار
SaaS — مرجع متعمّق
نظرة سريعة
قسم بعنوان «نظرة سريعة»توفّر وحدة SaaS في Enterprise اللبنات متعددة المستأجرين لخدمة قائمة على NextPDF.
TenantContextكائن قيمة هوية غير قابل للتغيير، يُحلّ من السياق المُصادَق عليه فقط.ApiKeyGeneratorوApiKeyAuthenticatorيُصدِران ويتحقّقان من مفاتيح API ذات بادئة ومجموع تحقّق ومُخزّنة كتجزئة.QuotaCheckerيضبط الطلبات وفق حصص كل مستأجر: تحذير عند 80%، ورفض عند 100%، ومنع بالفشل المغلق عندما يكون الاستخدام غير معروف.SidecarJwtMinterيسكّ رموز خدمة HS256 قصيرة العمر للنداءات بين المكوّنات.UsageMeterوStripeMeteringSyncerيسحبان أحداث الاستخدام ويزامنانها مع مزوّد الفوترة بعدم تكرار حتمي.
التوفّر والترخيص
قسم بعنوان «التوفّر والترخيص»تُشحن هذه الإمكانية في NextPDF Enterprise (nextpdf/enterprise) وتُفعَّل بغلاف ترخيص من فئة Enterprise. لا تُحمّل عمليةُ نشرٍ بلا ذلك الاستحقاق أصنافَ الإمكانية. قارن الإصدارات واحصل على ترخيص.
سطح SaaS إمكانية أساسية في Enterprise؛ ولا توجد راية منفصلة لكل ميزة. لا يملك NextPDF Core (Apache-2.0) وNextPDF Pro أي نموذج للاستئجار أو مفتاح API أو حصة؛ ولهذه الإمكانية لا يوجد مكافئ في فئة أدنى.
composer require nextpdf/enterprise:^3واجهة API العامة
قسم بعنوان «واجهة API العامة»تقع كل الرموز تحت NextPDF\Enterprise\SaaS.
| الرمز | المعاملات | السلوك الافتراضي | القيمة المُعادة | يرمي أو يفشل بـ | ملاحظات |
|---|---|---|---|---|---|
TenantContext | string $tenantId، string $source، array $scopes = ['read'] | كائن قيمة هوية غير قابل للتغيير | كائن قيمة | لا شيء | المصادر: jwt، mtls، api_key؛ وhasScope() / hasAnyScope() تختبران النطاقات |
TenantContext::singleTenant() | لا شيء | مستأجر default ثابت مع read، write، admin | TenantContext | لا شيء | عمليات النشر أحادية المستأجر |
ApiKeyAuthenticator::authenticate() | string $rawKey | تحقّق من ست خطوات، ثم حلّ السياق | TenantContext | ApiKeyAuthenticationException (HTTP 401) | سياق source هو api_key؛ وتُنسخ النطاقات من سجل المفتاح |
ApiKeyAuthenticator::requireScope() | TenantContext $context، ApiKeyScope $requiredScope | تأكيد صريح للنطاق | void | ApiKeyAuthenticationException::insufficientScope() (HTTP 403) | فرض النطاق خطوة منفصلة وصريحة |
ApiKeyGenerator::generateLive() / ::generateTest() | لا شيء | مفتاح جديد: بادئة، وجسم base62 من 32 حرفاً (192-bit من الإنتروبيا)، ومجموع تحقّق من 4 أحرف | array{key, hash, prefix} | لا شيء | البادئتان npf_live_ / npf_test_؛ وhash هو ملخّص التخزين |
ApiKeyGenerator::validateChecksum() | string $key | فحص شكل البادئة والطول ومجموع تحقّق CRC32 | bool | لا شيء | حماية من الأخطاء المطبعية قبل أي بحث في مخزن البيانات؛ وليست ضابطاً أمنياً |
ApiKeyGenerator::hashKey() (ساكنة) | string $key | ملخّص SHA-256 السداسي عشري للمفتاح الخام | string | لا شيء | التمثيل الوحيد المُخزَّن للمفتاح |
ApiKeyGenerator::isLiveKey() / ::isTestKey() | string $key | فحص البادئة | bool | لا شيء | البيئة مرئية دون بحث |
ApiKey | المعرّف، والمستأجر، وتجزئة المفتاح، وبادئة العرض، وقناع النطاق، ولحظات الإنشاء/الانتهاء/الإبطال | سجل مفتاح مُخزَّن؛ ولا يُحفظ النص الصريح أبداً | كائن قيمة | لا شيء | isActive()، isRevoked()، isExpired()، scopeNames() |
ApiKeyScope | تعداد مدعوم: Read = 1، Write = 2، Admin = 4 | نموذج نطاق بقناع بتّي | تعداد | لا شيء | maskFromNames()، fromName()، fullAccess()؛ ويتجاهل باني القناع الأسماء غير المعروفة |
ApiKeyRepositoryInterface | — | عقد التخزين؛ وحفظ التجزئة فقط | — | مُحدَّد بالتنفيذ | findByHash()، findActiveByTenant()، store()، revoke() |
SidecarJwtMinter::__construct() | string $secret، والمُصدِر، والجمهور، وint $ttlSeconds = 300 | يرفض سرّ التوقيع الأقل من 16 بايت عند الإنشاء | نسخة | InvalidArgumentException | حدّ أدنى لقوة المفتاح 128-bit؛ ويُوصى بـ 32 بايت عشوائياً أو أكثر |
SidecarJwtMinter::mint() | TenantContext $tenant | HS256 JWT مع iss، aud، sub، scope، tenant_id، iat، exp، jti | string | JsonException عند فشل ترميز المطالبة | عمر افتراضي خمس دقائق؛ وjti هو 16 بايت عشوائياً مُرمّزاً بالسداسي عشري |
QuotaChecker::check() | TenantContext $tenant، TenantQuota $quota | يقرأ الاستخدام الحالي؛ ويحذّر عند 80%؛ ويرفض عند 100%؛ ويمنع عندما يكون الاستخدام غير معروف | array{allowed: bool, warning_percentage: float|null} | QuotaExceededException، QuotaUnavailableException | يُستدعى ردّ نداء التنبيه عند كلا الحدّين |
TenantQuota | float $maxCuPerPeriod، والمجموعات، وبايتات التخزين، والمهام المتزامنة | حدود لكل فترة؛ وثابت حدّ ليّن 80% | كائن قيمة | لا شيء | افتراضات fromConfig(): 10,000 CU، و100 مجموعة، و10 GB، و10 مهام |
QuotaExceededException::toErrorEnvelope() | لا شيء | غلاف خطأ SPEC-QUOTA-001 | array | — | HTTP 402، غير قابل لإعادة المحاولة؛ ويحمل الحالي والحدّ ولحظة إعادة التعيين |
QuotaUnavailableException::toErrorEnvelope() | لا شيء | غلاف خطأ SPEC-QUOTA-503 | array | — | HTTP 503، قابل لإعادة المحاولة؛ والسبب usage_undeterminable |
UsageMeter::pullUsage() | array<string, int> $watermarks | يستطلع كل مضيف مصدر استخدام مُهيّأ من مؤشّره | array{events, instance_id} | UsageMeterException عندما يتعذّر الوصول إلى كل مضيف | يُحتمَل الانقطاع الجزئي؛ وتُسجَّل المضيفات المتعذّر الوصول إليها وتُتخطّى |
UsageMeter::getCurrentUsage() | string $tenantId | استخدام وحدات الحوسبة للفترة الحالية | float | UsageMeterException عندما يتعذّر تحديد الاستخدام | الصفر القابل للتحليل مرجعيّ؛ والاستخدام غير المعروف يرمي استثناءً |
StripeMeteringSyncer::sync() | array<string, int> $watermarks | دورة سحب وتحويل وإرسال واحدة | array{watermarks, sent, failed} | لا شيء؛ وتُوجَّه إخفاقات الإرسال إلى ردّ نداء DLQ | يُعيد فشل السحب دورة بلا عمل تحفظ المؤشّر |
StripeAdapter::sendMeterEvent() | MeterEvent $event | POST إلى المزوّد مع ترويسة عدم التكرار | void | StripeSyncException | HTTP 429 و5xx قابلان لإعادة المحاولة؛ وباقي 4xx غير قابل لإعادة المحاولة |
StripeAdapter::sendBatch() | list<MeterEvent> $events | يرسل كل حدث؛ ويجمع الإخفاقات | list<StripeSyncException> | لا شيء | القائمة الفارغة تعني نجاح كل حدث |
MeterEvent | اسم العدّاد، والمستأجر، والقيمة، ومفتاح عدم التكرار، والطابع الزمني | كائن قيمة حدث عدّاد غير قابل للتغيير | كائن قيمة | لا شيء | toStripePayload() تسلسل حمولة المزوّد |
final readonly class ApiKeyAuthenticator{ public function __construct( private ApiKeyRepositoryInterface $repository, private ApiKeyGenerator $generator, private LoggerInterface $logger, ) {}
public function authenticate(string $rawKey): TenantContext {}
public function requireScope(TenantContext $context, ApiKeyScope $requiredScope): void {}}final class QuotaChecker{ public function __construct( private readonly UsageMeterInterface $usageMeter, private readonly LoggerInterface $logger, private readonly Closure $quotaAlertCallback, ) {}
/** @return array{allowed: bool, warning_percentage: float|null} */ public function check(TenantContext $tenant, TenantQuota $quota): array {}}interface UsageMeterInterface{ /** @return array<string, mixed> */ public function pullUsage(array $watermarks): array;
public function getCurrentUsage(string $tenantId): float;}final class StripeMeteringSyncer{ public function __construct( private readonly UsageMeterInterface $usageMeter, private readonly StripeAdapterInterface $stripeAdapter, private readonly LoggerInterface $logger, private readonly Closure $dlqCallback, ) {}
/** @return array{watermarks: array<string, int>, sent: int, failed: int} */ public function sync(array $watermarks): array {}}final readonly class SidecarJwtMinter{ public function __construct( private string $secret, private string $issuer = 'nextpdf-enterprise', private string $audience = 'nextpdf-spectrum', private int $ttlSeconds = self::DEFAULT_TTL_SECONDS, ) {}
public function mint(TenantContext $tenant): string {}}عقد السلوك
قسم بعنوان «عقد السلوك»- هوية المستأجر. سياق المستأجر غير قابل للتغيير: معرّف المستأجر، ومصدر الحلّ، والنطاقات. تُحلّ الهوية من السياق المُصادَق عليه فقط (
jwt،mtls،api_key) — ولا تُحلّ أبداً من ترويسة أو معامِل استعلام يوفّرهما العميل. تستخدم عملية النشر أحادية المستأجر سياقdefaultالثابت بكامل النطاقات. - ترتيب المصادقة. تسير مصادقة مفتاح API بترتيب ثابت: مجموع التحقّق، وتجزئة SHA-256، والبحث في المستودع، وفحص الإبطال، وفحص الانتهاء، وحلّ السياق. المفاتيح غير المعروفة والمُبطَلة والمنتهية ثلاث نتائج متمايزة، كلها HTTP 401؛ والنطاق غير الكافي HTTP 403.
- سرّية المفتاح. لا يُخزَّن المفتاح الخام أو يُسجَّل أبداً؛ ويُحفظ ملخّصه SHA-256 فقط ويُبحث عنه. لا يُجري المُصادِق أي مقارنة بايتية للسرّ بنفسه؛ والبحث عن الملخّص بزمن ثابت هو عقد تنفيذ المستودع.
- حدود الحصة. عند الحدّ الليّن 80% يمضي الطلب، وتُعاد نسبة التحذير، ويُطلَق ردّ نداء التنبيه. وعند الحدّ الصارم 100% يُرفض الطلب بـ
SPEC-QUOTA-001(HTTP 402) حاملاً لحظة إعادة التعيين — أول يوم من الشهر التالي، منتصف الليل UTC. - فشل الحصة المغلق. الاستخدام المتعذّر تحديده يمنع الطلب بـ
SPEC-QUOTA-503(HTTP 503، قابل لإعادة المحاولة). لا يُعامَل الاستخدام غير المعروف كصفر أبداً. والاستخدام الصفري الحقيقي القابل للتحليل مرجعيّ ويسمح بالطلب. - إزالة تكرار التنبيهات. لا يزيل الفاحص تكرار التنبيهات؛ وإزالة التكرار لكل فترة مسؤولية ردّ النداء.
- مزامنة القياس. الدورة مُجدوَلة، ولا تكون أبداً على مسار الطلب. تستأنف من علامات مائية لكل مصدر وتُقدّم كل مؤشّر إلى أعلى هوية حدث أُرسل بنجاح. مفتاح عدم التكرار حتمي — المستأجر، والفترة، وهوية الحدث — لذا ينهار الحدث المُعاد إرساله على إزالة التكرار لدى المزوّد.
- فشل السحب. يُعيد السحب الفاشل دورة بلا عمل (
sent0،failed0) تحفظ العلامات المائية؛ وتُعيد الدورة التالية المحاولة على النافذة نفسها بدلاً من تخطّيها. - رموز الخدمة. الرموز من نوع HS256 بسرّ مشترك وتحمل
iss،aud،sub،scope،tenant_id،iat،exp، وjtiفريداً. العمر الافتراضي خمس دقائق. يرفض الإنشاء سرّاً أقل من 16 بايت، بفشل مغلق.
الحالات الحديّة وأنماط الفشل
قسم بعنوان «الحالات الحديّة وأنماط الفشل»- المفتاح المشوّه يفشل في مجموع التحقّق ويُرفض قبل أي وصول لمخزن البيانات. والمفتاح جيّد التكوين لكنه غير معروف يُرفض بعد البحث. وكلاهما يظهر كنتيجة مفتاح غير صالح.
- المفاتيح غير المعروفة والمُبطَلة والمنتهية تستخدم مصانع استثناء متمايزة؛ وراية
keyExpiredصحيحة فقط في النتيجة المنتهية. اربطها باستجابات عميل متمايزة. QuotaChecker::check()تعود فقط عند القبول؛ وقيمةallowedالمُعادة دائماًtrue. والرفض وعدم التوفّر نتيجتان استثنائيتان.TenantQuota::usagePercentage()تُعيد0.0للحصة غير الموجبة؛ وfromConfig()تستبدل الافتراضات بالقيم الغائبة وتُقيّد الحدود الصحيحة إلى 1 على الأقل.- العلامات المائية لكل مصدر؛ والعلامة المائية الغائبة تبدأ من بداية دفق ذلك المصدر (المؤشّر
0). وعملية النشر متعددة المصادر تحافظ على علامات مائية مستقلة. - يتخطّى التحويل الأحداث غير المصفوفة، والأحداث ذات العملية أو المستأجر الغائب أو الفارغ، والقيمة غير الموجبة، أو العملية غير المرتبطة — دون إفشال الدورة. ويُرفض الحدث الذي يفتقر إلى هوية عدد صحيح موجب صالحة للاستخدام مع تحذير: مفتاح احتياطي عشوائي سيُبطل إزالة التكرار من جهة المزوّد وقد يُفوتر المستأجر مرتين.
- عشرة إخفاقات إرسال متتالية تتصاعد إلى إدخال سجل حَرِج؛ ويُعاد ضبط العدّاد عند أي إرسال ناجح. وكل حدث فاشل يصل مع ذلك إلى ردّ نداء الرسائل الميتة.
- جسم JSON مشوّه من مضيف مصدر استخدام يُنتج قائمة أحداث فارغة، لا فشلاً في الدورة. و
pullUsage()ترمي استثناءً فقط عندما يتعذّر الوصول إلى كل مضيف مُهيّأ.
سلوك وضع FIPS
قسم بعنوان «سلوك وضع FIPS»- بدائيات الملخّص وMAC هي SHA-256 وHMAC-SHA256 عبر مزوّد التشفير PHP للمضيف. والبناء المقيّد بـ FIPS يفشل مغلقاً على خوارزمية غير معتمدة بدلاً من التخفيض؛ ولا تضيف طبقة SaaS أي سياسة تشفير خاصة بها.
- أجسام المفاتيح ومعرّفات الرموز تأتي من CSPRNG (
random_int()،random_bytes()). - مجموع تحقّق CRC32 ليس ضابطاً تشفيرياً ولا يتأثّر بوضع FIPS.
المطابقة
قسم بعنوان «المطابقة»تصف العبارات أدناه الإمكانية مقابل البنود المُستشهَد بها. وهي ليست ادّعاءات اعتماد؛ ولا يملك NextPDF أي اعتماد لهذه الوحدة.
| السلوك | المرجع |
|---|---|
دلالات عدم-بعد لـ exp رمز الخدمة | RFC 7519 §4.1.4 |
| تسلسل JWS المضغوط لرمز الخدمة | RFC 7515 §3.1 |
| حدّ أدنى لسرّ HS256 مقداره 16 بايت؛ ولا كلمات مرور يحفظها البشر كمفاتيح MAC | RFC 8725 §3.5 (threat: §2.2) |
| عقد البحث عن الملخّص بزمن ثابت في المستودع | OWASP ASVS 5.0 §11.2.4 |
| ملخّص تخزين مفتاح API بـ SHA-256 | FIPS 180-4 (code-declared) |
استشهادات RFC 8725 وOWASP ASVS 5.0 مُتحقَّق منها عبر RAG؛ ومعرّفات المراجع الكاملة مُسجَّلة في مقدّمة هذه الصفحة. ومراجع FIPS 180-4 وFIPS 198-1 وBSI TR-02102-1 مُعلَنة في الكود ضمن مصدر المنتج (hash('sha256', …) وحدّ المفتاح المُوثّق للساكّ)؛ ولم تُسترجَع من مجموعة RAG لهذه الصفحة. ومتطلّب الزمن الثابت في ASVS §11.2.4 يُلزِم تنفيذ المستودع الذي يوفّره المشغّل، لا صنف المُصادِق نفسه.
ملاحظات التطوير
قسم بعنوان «ملاحظات التطوير»- وفّر تنفيذات دائمة لـ
ApiKeyRepositoryInterfaceوStripeAdapterInterface؛ فالحزمة تشحن العقود وعميل مزوّد PSR-18، لا الحفظ. - التبعيات تجريدات PSR فقط: مُسجِّل PSR-3، وعميل PSR-18 HTTP، ومصنعا طلب ودفق PSR-17. ولا يلزم أي SDK للمزوّد.
- شغّل مزامنة القياس كمهمة مُجدوَلة. واحفظ العلامات المائية المُعادة بدوام بعد كل دورة.
- اعرض نسبة تحذير الحصة للعملاء، مثلاً كترويسة تحذير، وأزل تكرار تنبيهات الحصة لكل فترة في ردّ النداء.
- زوّد سرّ ساكّ الرموز من التهيئة كقيمة عشوائية عالية الإنتروبيا؛ ويُوصى بـ 32 بايت عشوائياً أو أكثر. ولا تشتقّه من كلمة مرور أبداً.
- بادئات المفاتيح تجعل البيئة مرئية دون بحث؛ ولا تتصادم مفاتيح البيئة التجريبية والإنتاج أبداً لأن البادئة تشارك في الملخّص المُخزَّن.
- تفاصيل الآلية الداخلية تبقى في التوثيق الداخلي لمستودع المصدر وهي خارج نطاق هذا الدليل.
حدود النشر
قسم بعنوان «حدود النشر»توثّق هذه الصفحة السلوك القابل للملاحظة خارجياً وسطح API العام المدعوم فقط. أما مسارات فضاء الأسماء الداخلية، والأصناف المساعِدة، وجداول الآلية، وأسماء ملفات كتيّب التشغيل، وبادئات التذاكر فهي خارج النطاق.