Enterprise phiên bản
SaaS — Tài liệu tham chiếu chuyên sâu
Tổng quan nhanh
Phần tiêu đề “Tổng quan nhanh”Module SaaS Enterprise cung cấp các khối xây dựng đa thuê bao cho một dịch vụ dựa trên NextPDF.
TenantContextlà một value object danh tính bất biến, chỉ được phân giải từ ngữ cảnh đã xác thực.ApiKeyGeneratorvàApiKeyAuthenticatorphát hành và xác thực các API key có prefix, có checksum, lưu dưới dạng hash.QuotaCheckerkiểm soát yêu cầu theo hạn mức của từng thuê bao: cảnh báo ở 80%, từ chối ở 100%, và fail-closed khi mức sử dụng chưa xác định.SidecarJwtMinterphát hành các service token HS256 ngắn hạn cho các lệnh gọi liên thành phần.UsageMetervàStripeMeteringSyncerkéo các sự kiện sử dụng và đồng bộ chúng tới nhà cung cấp lập hóa đơn với tính idempotency tất định.
Tính khả dụng và cấp phép
Phần tiêu đề “Tính khả dụng và cấp phép”Năng lực này đi kèm trong NextPDF Enterprise (nextpdf/enterprise) và được kích hoạt bằng một license envelope bậc Enterprise. Một triển khai không có quyền đó sẽ không nạp các lớp của năng lực này. So sánh các phiên bản và lấy giấy phép.
Bề mặt SaaS là một năng lực Enterprise cơ sở; không tồn tại cờ riêng theo từng tính năng. NextPDF Core (Apache-2.0) và NextPDF Pro không có mô hình đa thuê bao, API key, hay hạn mức nào; năng lực này không có tương đương ở bậc thấp hơn.
composer require nextpdf/enterprise:^3Bề mặt API công khai
Phần tiêu đề “Bề mặt API công khai”Tất cả các symbol nằm dưới NextPDF\Enterprise\SaaS.
| Symbol | Tham số | Hành vi mặc định | Trả về | Ném hoặc thất bại với | Ghi chú |
|---|---|---|---|---|---|
TenantContext | string $tenantId, string $source, array $scopes = ['read'] | Value object danh tính bất biến | value object | Không | Nguồn: jwt, mtls, api_key; hasScope() / hasAnyScope() kiểm tra scope |
TenantContext::singleTenant() | không có | Thuê bao default cố định với read, write, admin | TenantContext | Không | Triển khai đơn thuê bao |
ApiKeyAuthenticator::authenticate() | string $rawKey | Xác thực sáu bước, rồi phân giải context | TenantContext | ApiKeyAuthenticationException (HTTP 401) | source của context là api_key; scope được sao chép từ bản ghi key |
ApiKeyAuthenticator::requireScope() | TenantContext $context, ApiKeyScope $requiredScope | Khẳng định scope tường minh | void | ApiKeyAuthenticationException::insufficientScope() (HTTP 403) | Việc thực thi scope là một bước riêng biệt, tường minh |
ApiKeyGenerator::generateLive() / ::generateTest() | không có | Key mới: prefix, thân base62 32 ký tự (entropy 192-bit), checksum 4 ký tự | array{key, hash, prefix} | Không | Prefix npf_live_ / npf_test_; hash là digest lưu trữ |
ApiKeyGenerator::validateChecksum() | string $key | Kiểm tra hình dạng prefix, độ dài, và CRC32-checksum | bool | Không | Bộ chặn lỗi gõ nhầm trước mọi tra cứu kho dữ liệu; không phải một biện pháp kiểm soát bảo mật |
ApiKeyGenerator::hashKey() (static) | string $key | Digest hex SHA-256 của key thô | string | Không | Biểu diễn được lưu trữ duy nhất của một key |
ApiKeyGenerator::isLiveKey() / ::isTestKey() | string $key | Kiểm tra prefix | bool | Không | Môi trường hiển thị mà không cần tra cứu |
ApiKey | id, tenant, hash của key, prefix hiển thị, scope mask, các thời điểm created/expires/revoked | Bản ghi key được lưu trữ; văn bản rõ không bao giờ được lưu | value object | Không | isActive(), isRevoked(), isExpired(), scopeNames() |
ApiKeyScope | backed enum: Read = 1, Write = 2, Admin = 4 | Mô hình scope kiểu bitmask | enum | Không | maskFromNames(), fromName(), fullAccess(); các tên không xác định bị bộ dựng mask bỏ qua |
ApiKeyRepositoryInterface | — | Hợp đồng lưu trữ; chỉ lưu hash | — | Do triển khai định nghĩa | findByHash(), findActiveByTenant(), store(), revoke() |
SidecarJwtMinter::__construct() | string $secret, issuer, audience, int $ttlSeconds = 300 | Từ chối một secret ký dưới 16 byte tại thời điểm khởi tạo | instance | InvalidArgumentException | Sàn độ mạnh khóa 128-bit; khuyến nghị 32 byte ngẫu nhiên trở lên |
SidecarJwtMinter::mint() | TenantContext $tenant | JWT HS256 với iss, aud, sub, scope, tenant_id, iat, exp, jti | string | JsonException khi mã hóa claim thất bại | Thời hạn mặc định năm phút; jti là 16 byte ngẫu nhiên, mã hóa hex |
QuotaChecker::check() | TenantContext $tenant, TenantQuota $quota | Đọc mức sử dụng hiện tại; cảnh báo ở 80%; từ chối ở 100%; từ chối khi mức sử dụng chưa xác định | array{allowed: bool, warning_percentage: float|null} | QuotaExceededException, QuotaUnavailableException | Callback cảnh báo được gọi ở cả hai ngưỡng |
TenantQuota | float $maxCuPerPeriod, collections, storage bytes, concurrent jobs | Giới hạn theo từng kỳ; hằng số ngưỡng mềm 80% | value object | Không | Mặc định của fromConfig(): 10,000 CU, 100 collections, 10 GB, 10 jobs |
QuotaExceededException::toErrorEnvelope() | không có | Error envelope SPEC-QUOTA-001 | array | — | HTTP 402, không thể thử lại; mang theo mức hiện tại, giới hạn, và thời điểm đặt lại |
QuotaUnavailableException::toErrorEnvelope() | không có | Error envelope SPEC-QUOTA-503 | array | — | HTTP 503, có thể thử lại; lý do usage_undeterminable |
UsageMeter::pullUsage() | array<string, int> $watermarks | Thăm dò mọi host nguồn sử dụng đã cấu hình từ con trỏ của nó | array{events, instance_id} | UsageMeterException khi mọi host đều không thể tiếp cận | Chấp nhận gián đoạn một phần; các host không tiếp cận được sẽ được ghi nhật ký và bỏ qua |
UsageMeter::getCurrentUsage() | string $tenantId | Mức sử dụng compute-unit của kỳ hiện tại | float | UsageMeterException khi mức sử dụng không thể xác định | Một số 0 phân tích được là có thẩm quyền; mức sử dụng chưa xác định sẽ ném lỗi |
StripeMeteringSyncer::sync() | array<string, int> $watermarks | Một chu kỳ kéo, biến đổi, gửi | array{watermarks, sent, failed} | Không; các lần gửi thất bại được định tuyến tới callback DLQ | Kéo thất bại trả về một chu kỳ no-op giữ nguyên con trỏ |
StripeAdapter::sendMeterEvent() | MeterEvent $event | POST tới nhà cung cấp với một header idempotency | void | StripeSyncException | HTTP 429 và 5xx có thể thử lại; các 4xx khác không thể thử lại |
StripeAdapter::sendBatch() | list<MeterEvent> $events | Gửi mỗi sự kiện; thu thập các lần thất bại | list<StripeSyncException> | Không | Danh sách rỗng nghĩa là mọi sự kiện đều thành công |
MeterEvent | tên meter, tenant, value, idempotency key, timestamp | Value object meter-event bất biến | value object | Không | toStripePayload() tuần tự hóa payload của nhà cung cấp |
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 {}}Hợp đồng hành vi
Phần tiêu đề “Hợp đồng hành vi”- Danh tính thuê bao. Một tenant context là bất biến: mã định danh thuê bao, nguồn phân giải, các scope. Danh tính chỉ được phân giải từ ngữ cảnh đã xác thực (
jwt,mtls,api_key) — không bao giờ từ một header hay tham số truy vấn do client cung cấp. Một triển khai đơn thuê bao dùng contextdefaultcố định với đầy đủ scope. - Thứ tự xác thực. Xác thực bằng API key diễn ra theo một thứ tự cố định: checksum, hash SHA-256, tra cứu repository, kiểm tra thu hồi, kiểm tra hết hạn, phân giải context. Các key không xác định, đã thu hồi, và đã hết hạn là ba kết quả riêng biệt, tất cả đều HTTP 401; thiếu scope là HTTP 403.
- Bí mật của key. Key thô không bao giờ được lưu trữ hay ghi nhật ký; chỉ digest SHA-256 của nó được lưu và tra cứu. Bản thân bộ xác thực không thực hiện phép so sánh bí mật theo từng byte nào; việc tra cứu digest an toàn về thời gian là hợp đồng của phần triển khai repository.
- Ngưỡng hạn mức. Ở giới hạn mềm 80%, yêu cầu vẫn tiếp tục, tỷ lệ phần trăm cảnh báo được trả về, và callback cảnh báo được kích hoạt. Ở giới hạn cứng 100%, yêu cầu bị từ chối với
SPEC-QUOTA-001(HTTP 402) mang theo thời điểm đặt lại — ngày đầu tiên của tháng kế tiếp, nửa đêm UTC. - Hạn mức fail-closed. Mức sử dụng không thể xác định sẽ từ chối yêu cầu với
SPEC-QUOTA-503(HTTP 503, có thể thử lại). Mức sử dụng chưa xác định không bao giờ được coi là 0. Một mức sử dụng 0 thực sự, phân tích được, là có thẩm quyền và cho phép đi qua. - Loại trùng cảnh báo. Bộ kiểm tra không loại trùng cảnh báo; việc loại trùng theo từng kỳ là trách nhiệm của callback.
- Đồng bộ đo lường. Chu kỳ được lên lịch, không bao giờ nằm trên đường yêu cầu. Nó tiếp tục từ các watermark theo từng nguồn và đẩy mỗi con trỏ tiến tới danh tính sự kiện được gửi thành công cao nhất. Idempotency key là tất định — thuê bao, kỳ, danh tính sự kiện — nên một sự kiện được gửi lại sẽ gộp lại nhờ loại trùng phía nhà cung cấp.
- Kéo thất bại. Một lần kéo thất bại trả về một chu kỳ no-op (
sent0,failed0) giữ nguyên các watermark; chu kỳ kế tiếp thử lại cùng cửa sổ đó thay vì bỏ qua nó. - Service token. Các token là HS256 với một secret dùng chung và mang theo
iss,aud,sub,scope,tenant_id,iat,exp, và mộtjtiduy nhất. Thời hạn mặc định là năm phút. Việc khởi tạo từ chối một secret dưới 16 byte, fail-closed.
Trường hợp biên và chế độ lỗi
Phần tiêu đề “Trường hợp biên và chế độ lỗi”- Một key dị dạng thất bại ở checksum và bị từ chối trước bất kỳ truy cập kho dữ liệu nào. Một key đúng định dạng nhưng không xác định bị từ chối sau khi tra cứu. Cả hai đều hiện ra như kết quả key-không-hợp-lệ.
- Các key không xác định, đã thu hồi, và đã hết hạn dùng các factory ngoại lệ riêng biệt; cờ
keyExpiredchỉ đúng ở kết quả hết hạn. Hãy ánh xạ chúng tới các phản hồi client riêng biệt. QuotaChecker::check()chỉ trả về khi cho phép đi qua; giá trịallowedđược trả về luôn làtrue. Từ chối và không sẵn dùng là các kết quả ngoại lệ.TenantQuota::usagePercentage()trả về0.0cho một hạn mức không dương;fromConfig()thay các giá trị vắng mặt bằng mặc định và kẹp các giới hạn nguyên về ít nhất 1.- Các watermark là theo từng nguồn; một watermark thiếu sẽ bắt đầu từ đầu luồng của nguồn đó (con trỏ
0). Một triển khai đa nguồn duy trì các watermark độc lập. - Bước biến đổi bỏ qua các sự kiện không phải mảng, các sự kiện thiếu hoặc rỗng ở thao tác hoặc thuê bao, một giá trị không dương, hoặc một thao tác chưa được ánh xạ — mà không làm thất bại chu kỳ. Một sự kiện thiếu một danh tính số nguyên dương dùng được sẽ bị từ chối kèm một cảnh báo: một khóa dự phòng ngẫu nhiên sẽ phá hỏng việc loại trùng phía nhà cung cấp và có thể tính phí trùng cho thuê bao.
- Mười lần gửi thất bại liên tiếp leo thang thành một mục nhật ký nghiêm trọng; bộ đếm được đặt lại khi có bất kỳ lần gửi thành công nào. Mọi sự kiện thất bại vẫn đến được callback dead-letter.
- Một body JSON dị dạng từ một host nguồn sử dụng cho ra một danh sách sự kiện rỗng, không phải một lần thất bại chu kỳ.
pullUsage()chỉ ném lỗi khi mọi host đã cấu hình đều không thể tiếp cận.
Hành vi ở chế độ FIPS
Phần tiêu đề “Hành vi ở chế độ FIPS”- Các primitive digest và MAC là SHA-256 và HMAC-SHA256 thông qua nhà cung cấp mã hóa PHP của host. Một bản build bị ràng buộc FIPS sẽ fail closed trên một thuật toán không được chấp thuận thay vì hạ cấp; lớp SaaS không thêm chính sách mã hóa riêng nào.
- Thân key và mã định danh token đến từ CSPRNG (
random_int(),random_bytes()). - Checksum CRC32 không phải một biện pháp kiểm soát mã hóa và không bị ảnh hưởng bởi chế độ FIPS.
Tính phù hợp
Phần tiêu đề “Tính phù hợp”Các phát biểu dưới đây mô tả năng lực đối chiếu với các điều khoản được trích dẫn. Chúng không phải là tuyên bố chứng nhận; NextPDF không nắm giữ chứng nhận nào cho module này.
| Hành vi | Tham chiếu |
|---|---|
Ngữ nghĩa not-after của exp trên service token | RFC 7519 §4.1.4 |
| Tuần tự hóa nén JWS của service token | RFC 7515 §3.1 |
| Sàn secret HS256 16 byte; không dùng mật khẩu con người nhớ được làm khóa MAC | RFC 8725 §3.5 (threat: §2.2) |
| Hợp đồng tra cứu digest an toàn về thời gian của repository | OWASP ASVS 5.0 §11.2.4 |
| Digest lưu trữ API key SHA-256 | FIPS 180-4 (code-declared) |
Các trích dẫn RFC 8725 và OWASP ASVS 5.0 được xác minh bằng RAG; các mã định danh tham chiếu đầy đủ được ghi trong frontmatter của trang này. Các tham chiếu FIPS 180-4, FIPS 198-1, và BSI TR-02102-1 được khai báo trong mã của mã nguồn sản phẩm (hash('sha256', …) và sàn khóa được ghi trong tài liệu của bộ minter); chúng không được truy xuất từ kho RAG cho trang này. Yêu cầu an toàn về thời gian của ASVS §11.2.4 ràng buộc phần triển khai repository do người vận hành cung cấp, chứ không phải bản thân lớp authenticator.
Lưu ý phát triển
Phần tiêu đề “Lưu ý phát triển”- Hãy cung cấp các phần triển khai bền vững cho
ApiKeyRepositoryInterfacevàStripeAdapterInterface; gói cung cấp các hợp đồng và một client nhà cung cấp PSR-18, chứ không phải phần lưu trữ. - Các phụ thuộc chỉ là các abstraction PSR: PSR-3 logger, PSR-18 HTTP client, các factory request và stream PSR-17. Không cần SDK của nhà cung cấp nào.
- Hãy chạy đồng bộ đo lường như một job theo lịch. Lưu bền vững các watermark được trả về sau mỗi chu kỳ.
- Hãy đưa tỷ lệ phần trăm cảnh báo hạn mức ra cho client, ví dụ như một header cảnh báo, và loại trùng các cảnh báo hạn mức theo từng kỳ trong callback.
- Hãy cấp secret cho bộ minter token từ cấu hình dưới dạng một giá trị ngẫu nhiên entropy cao; khuyến nghị 32 byte ngẫu nhiên trở lên. Không bao giờ suy dẫn nó từ một mật khẩu.
- Prefix của key làm cho môi trường hiển thị mà không cần tra cứu; các key sandbox và production không bao giờ đụng nhau vì prefix tham gia vào digest được lưu trữ.
- Chi tiết cơ chế nội bộ nằm trong tài liệu nội bộ của repository nguồn và nằm ngoài phạm vi của tài liệu hướng dẫn này.
Ranh giới xuất bản
Phần tiêu đề “Ranh giới xuất bản”Trang này chỉ ghi lại hành vi có thể quan sát được từ bên ngoài và bề mặt API công khai được hỗ trợ. Các đường dẫn namespace nội bộ, các lớp helper, các bảng cơ chế, các tên tệp runbook, và các tiền tố ticket đều nằm ngoài phạm vi.