Enterprise edycja
SaaS — szczegółowa referencja
W skrócie
Dział zatytułowany „W skrócie”Moduł Enterprise SaaS dostarcza wielodostępne bloki konstrukcyjne dla usługi opartej na NextPDF.
TenantContextto niezmienny obiekt wartości tożsamości, rozwiązywany wyłącznie z uwierzytelnionego kontekstu.ApiKeyGeneratoriApiKeyAuthenticatorwydają i walidują klucze API z prefiksem, sumą kontrolną i przechowywaniem w postaci skrótu.QuotaCheckerbramkuje żądania względem limitów przypadających na najemcę: ostrzeżenie przy 80%, odrzucenie przy 100%, odmowa w trybie fail-closed, gdy zużycie jest nieznane.SidecarJwtMinterwytwarza krótkotrwałe tokeny usługowe HS256 na potrzeby wywołań między komponentami.UsageMeteriStripeMeteringSyncerpobierają zdarzenia zużycia i synchronizują je z dostawcą rozliczeń z deterministyczną idempotencją.
Dostępność i licencjonowanie
Dział zatytułowany „Dostępność i licencjonowanie”Ta możliwość jest dostarczana w NextPDF Enterprise (nextpdf/enterprise) i aktywuje się wraz z kopertą licencyjną poziomu Enterprise. Wdrożenie bez tego uprawnienia nie ładuje klas tej możliwości. Porównaj edycje i uzyskaj licencję.
Powierzchnia SaaS to podstawowa możliwość Enterprise; nie istnieje osobna flaga dla poszczególnych funkcji. NextPDF Core (Apache-2.0) i NextPDF Pro nie mają modelu wielodostępności, kluczy API ani limitów; ta możliwość nie ma odpowiednika na niższym poziomie.
composer require nextpdf/enterprise:^3Publiczna powierzchnia API
Dział zatytułowany „Publiczna powierzchnia API”Wszystkie symbole znajdują się w przestrzeni NextPDF\Enterprise\SaaS.
| Symbol | Parametry | Zachowanie domyślne | Zwraca | Zgłasza lub kończy się błędem | Uwagi |
|---|---|---|---|---|---|
TenantContext | string $tenantId, string $source, array $scopes = ['read'] | Niezmienny obiekt wartości tożsamości | obiekt wartości | Nic | Źródła: jwt, mtls, api_key; hasScope() / hasAnyScope() testują zakresy |
TenantContext::singleTenant() | brak | Stały najemca default z read, write, admin | TenantContext | Nic | Wdrożenia jednonajemcowe |
ApiKeyAuthenticator::authenticate() | string $rawKey | Sześcioetapowa walidacja, następnie rozwiązanie kontekstu | TenantContext | ApiKeyAuthenticationException (HTTP 401) | source kontekstu to api_key; zakresy kopiowane z rekordu klucza |
ApiKeyAuthenticator::requireScope() | TenantContext $context, ApiKeyScope $requiredScope | Jawne stwierdzenie zakresu | void | ApiKeyAuthenticationException::insufficientScope() (HTTP 403) | Egzekwowanie zakresu to osobny, jawny krok |
ApiKeyGenerator::generateLive() / ::generateTest() | brak | Nowy klucz: prefiks, 32-znakowy korpus base62 (entropia 192-bitowa), 4-znakowa suma kontrolna | array{key, hash, prefix} | Nic | Prefiksy npf_live_ / npf_test_; hash to skrót przechowywania |
ApiKeyGenerator::validateChecksum() | string $key | Kontrola kształtu prefiksu, długości i sumy kontrolnej CRC32 | bool | Nic | Zabezpieczenie przed literówką przed jakimkolwiek wyszukaniem w magazynie danych; nie jest mechanizmem bezpieczeństwa |
ApiKeyGenerator::hashKey() (statyczna) | string $key | Szesnastkowy skrót SHA-256 surowego klucza | string | Nic | Jedyna przechowywana reprezentacja klucza |
ApiKeyGenerator::isLiveKey() / ::isTestKey() | string $key | Sprawdzenie prefiksu | bool | Nic | Środowisko widoczne bez wyszukiwania |
ApiKey | id, najemca, skrót klucza, wyświetlany prefiks, maska zakresów, momenty utworzenia/wygaśnięcia/unieważnienia | Przechowywany rekord klucza; tekst jawny nigdy nie jest utrwalany | obiekt wartości | Nic | isActive(), isRevoked(), isExpired(), scopeNames() |
ApiKeyScope | enum z wartością: Read = 1, Write = 2, Admin = 4 | Model zakresów jako maska bitowa | enum | Nic | maskFromNames(), fromName(), fullAccess(); nieznane nazwy są ignorowane przez konstruktor maski |
ApiKeyRepositoryInterface | — | Kontrakt przechowywania; utrwalanie wyłącznie skrótu | — | Zależne od implementacji | findByHash(), findActiveByTenant(), store(), revoke() |
SidecarJwtMinter::__construct() | string $secret, issuer, audience, int $ttlSeconds = 300 | Odrzuca sekret podpisujący krótszy niż 16 bajtów podczas konstrukcji | instancja | InvalidArgumentException | Dolny próg siły klucza 128 bitów; zalecane 32 lub więcej losowych bajtów |
SidecarJwtMinter::mint() | TenantContext $tenant | JWT HS256 z iss, aud, sub, scope, tenant_id, iat, exp, jti | string | JsonException przy niepowodzeniu kodowania roszczeń | Domyślny czas życia pięć minut; jti to 16 losowych bajtów zakodowanych szesnastkowo |
QuotaChecker::check() | TenantContext $tenant, TenantQuota $quota | Odczytuje bieżące zużycie; ostrzega przy 80%; odrzuca przy 100%; odmawia, gdy zużycie jest nieznane | array{allowed: bool, warning_percentage: float|null} | QuotaExceededException, QuotaUnavailableException | Wywołanie zwrotne alertu wywoływane przy obu progach |
TenantQuota | float $maxCuPerPeriod, kolekcje, bajty magazynu, równoległe zadania | Limity na okres; stała progu miękkiego 80% | obiekt wartości | Nic | Wartości domyślne fromConfig(): 10,000 CU, 100 kolekcji, 10 GB, 10 zadań |
QuotaExceededException::toErrorEnvelope() | brak | Koperta błędu SPEC-QUOTA-001 | array | — | HTTP 402, bez ponawiania; niesie bieżące zużycie, limit oraz moment resetu |
QuotaUnavailableException::toErrorEnvelope() | brak | Koperta błędu SPEC-QUOTA-503 | array | — | HTTP 503, z możliwością ponowienia; powód usage_undeterminable |
UsageMeter::pullUsage() | array<string, int> $watermarks | Odpytuje każdy skonfigurowany host źródła zużycia od jego kursora | array{events, instance_id} | UsageMeterException, gdy każdy host jest nieosiągalny | Częściowa awaria tolerowana; nieosiągalne hosty logowane i pomijane |
UsageMeter::getCurrentUsage() | string $tenantId | Zużycie jednostek obliczeniowych w bieżącym okresie | float | UsageMeterException, gdy zużycie jest nieokreślalne | Dające się sparsować zero jest autorytatywne; nieznane zużycie zgłasza wyjątek |
StripeMeteringSyncer::sync() | array<string, int> $watermarks | Jeden cykl pobrania, transformacji i wysyłki | array{watermarks, sent, failed} | Nic; niepowodzenia wysyłki trafiają do wywołania zwrotnego DLQ | Niepowodzenie pobrania zwraca cykl bez działania zachowujący kursor |
StripeAdapter::sendMeterEvent() | MeterEvent $event | POST do dostawcy z nagłówkiem idempotencji | void | StripeSyncException | HTTP 429 i 5xx z możliwością ponowienia; pozostałe 4xx bez ponawiania |
StripeAdapter::sendBatch() | list<MeterEvent> $events | Wysyła każde zdarzenie; zbiera niepowodzenia | list<StripeSyncException> | Nic | Pusta lista oznacza, że każde zdarzenie się powiodło |
MeterEvent | nazwa miernika, najemca, wartość, klucz idempotencji, znacznik czasu | Niezmienny obiekt wartości zdarzenia pomiarowego | obiekt wartości | Nic | toStripePayload() serializuje ładunek dostawcy |
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 {}}Kontrakt zachowania
Dział zatytułowany „Kontrakt zachowania”- Tożsamość najemcy. Kontekst najemcy jest niezmienny: identyfikator najemcy, źródło rozwiązania, zakresy. Tożsamość jest rozwiązywana wyłącznie z uwierzytelnionego kontekstu (
jwt,mtls,api_key) — nigdy z nagłówka ani parametru zapytania dostarczonego przez klienta. Wdrożenie jednonajemcowe używa stałego kontekstudefaultz pełnymi zakresami. - Kolejność uwierzytelniania. Uwierzytelnianie kluczem API przebiega w ustalonej kolejności: suma kontrolna, skrót SHA-256, wyszukanie w repozytorium, kontrola unieważnienia, kontrola wygaśnięcia, rozwiązanie kontekstu. Klucze nieznane, unieważnione i wygasłe to trzy odrębne wyniki, wszystkie HTTP 401; niewystarczający zakres to HTTP 403.
- Tajność klucza. Surowy klucz nigdy nie jest przechowywany ani logowany; utrwalany i wyszukiwany jest wyłącznie jego skrót SHA-256. Uwierzytelniacz sam nie wykonuje bajtowego porównania sekretu; wyszukanie skrótu w czasie stałym to kontrakt implementacji repozytorium.
- Progi limitu. Przy limicie miękkim 80% żądanie przebiega dalej, zwracany jest procent ostrzeżenia i uruchamiane jest wywołanie zwrotne alertu. Przy limicie twardym 100% żądanie jest odrzucane z
SPEC-QUOTA-001(HTTP 402) niosącym moment resetu — pierwszy dzień następnego miesiąca, północ UTC. - Limit w trybie fail-closed. Nieokreślalne zużycie powoduje odmowę żądania z
SPEC-QUOTA-503(HTTP 503, z możliwością ponowienia). Nieznane zużycie nigdy nie jest traktowane jako zero. Rzeczywiste, dające się sparsować zerowe zużycie jest autorytatywne i dopuszcza żądanie. - Deduplikacja alertów. Kontroler nie deduplikuje alertów; deduplikacja w obrębie okresu należy do wywołania zwrotnego.
- Synchronizacja pomiarów. Cykl jest zaplanowany, nigdy nie na ścieżce żądania. Wznawia od znaczników wodnych dla poszczególnych źródeł i przesuwa każdy kursor do najwyższej pomyślnie wysłanej tożsamości zdarzenia. Klucz idempotencji jest deterministyczny — najemca, okres, tożsamość zdarzenia — więc ponownie wysłane zdarzenie zostaje scalone przez deduplikację po stronie dostawcy.
- Niepowodzenie pobrania. Nieudane pobranie zwraca cykl bez działania (
sent0,failed0), który zachowuje znaczniki wodne; następny cykl ponawia to samo okno zamiast je pominąć. - Tokeny usługowe. Tokeny to HS256 ze współdzielonym sekretem i niosą
iss,aud,sub,scope,tenant_id,iat,exporaz unikalnyjti. Domyślny czas życia to pięć minut. Konstrukcja odrzuca sekret krótszy niż 16 bajtów w trybie fail-closed.
Przypadki brzegowe i tryby awarii
Dział zatytułowany „Przypadki brzegowe i tryby awarii”- Zniekształcony klucz nie przechodzi sumy kontrolnej i jest odrzucany przed jakimkolwiek dostępem do magazynu danych. Klucz poprawnie zbudowany, lecz nieznany, jest odrzucany po wyszukaniu. Oba ujawniają się jako wynik nieprawidłowego klucza.
- Klucze nieznane, unieważnione i wygasłe używają odrębnych fabryk wyjątków; flaga
keyExpiredjest prawdziwa tylko w wyniku wygaśnięcia. Odwzoruj je na odrębne odpowiedzi dla klienta. QuotaChecker::check()zwraca wartość tylko przy dopuszczeniu; zwróconeallowedjest zawszetrue. Odrzucenie i niedostępność to wyniki wyjątkowe.TenantQuota::usagePercentage()zwraca0.0dla niedodatniego limitu;fromConfig()podstawia wartości domyślne za brakujące i ogranicza limity całkowite do co najmniej 1.- Znaczniki wodne są dla poszczególnych źródeł; brakujący znacznik wodny startuje od początku strumienia danego źródła (kursor
0). Wdrożenie wieloźródłowe utrzymuje niezależne znaczniki wodne. - Transformacja pomija zdarzenia niebędące tablicą, zdarzenia z brakującą lub pustą operacją albo najemcą, niedodatnią wartością lub nieodwzorowaną operacją — bez powodowania niepowodzenia cyklu. Zdarzenie pozbawione użytecznej dodatniej całkowitej tożsamości jest odrzucane z ostrzeżeniem: losowy klucz zastępczy zniweczyłby deduplikację po stronie dostawcy i mógłby doprowadzić do podwójnego obciążenia najemcy.
- Dziesięć kolejnych niepowodzeń wysyłki eskaluje do krytycznego wpisu w logu; licznik jest zerowany przy dowolnej udanej wysyłce. Każde nieudane zdarzenie i tak trafia do wywołania zwrotnego dead-letter.
- Zniekształcony ładunek JSON z hosta źródła zużycia daje pustą listę zdarzeń, a nie niepowodzenie cyklu.
pullUsage()zgłasza wyjątek tylko wtedy, gdy każdy skonfigurowany host jest nieosiągalny.
Zachowanie w trybie FIPS
Dział zatytułowany „Zachowanie w trybie FIPS”- Prymitywy skrótu i MAC to SHA-256 oraz HMAC-SHA256 poprzez dostawcę kryptografii PHP hosta. Kompilacja ograniczona do FIPS kończy w trybie fail-closed na niezatwierdzonym algorytmie zamiast obniżać poziom; warstwa SaaS nie dodaje własnej polityki kryptograficznej.
- Korpusy kluczy i identyfikatory tokenów pochodzą z CSPRNG (
random_int(),random_bytes()). - Suma kontrolna CRC32 nie jest mechanizmem kryptograficznym i nie podlega trybowi FIPS.
Zgodność
Dział zatytułowany „Zgodność”Poniższe stwierdzenia opisują możliwość względem przywołanych klauzul. Nie są roszczeniami certyfikacyjnymi; NextPDF nie posiada certyfikacji dla tego modułu.
| Zachowanie | Odniesienie |
|---|---|
Semantyka not-after exp tokenu usługowego | RFC 7519 §4.1.4 |
| Kompaktowa serializacja JWS tokenu usługowego | RFC 7515 §3.1 |
| Dolny próg 16-bajtowego sekretu HS256; brak haseł zapamiętywalnych przez człowieka jako kluczy MAC | RFC 8725 §3.5 (zagrożenie: §2.2) |
| Kontrakt wyszukania skrótu w repozytorium w czasie stałym | OWASP ASVS 5.0 §11.2.4 |
| Skrót przechowywania klucza API SHA-256 | FIPS 180-4 (zadeklarowane w kodzie) |
Cytowania RFC 8725 oraz OWASP ASVS 5.0 są zweryfikowane przez RAG; pełne identyfikatory odniesień zapisano w bloku frontmatter tej strony. Odniesienia FIPS 180-4, FIPS 198-1 oraz BSI TR-02102-1 są zadeklarowane w kodzie źródłowym produktu (hash('sha256', …) oraz udokumentowany dolny próg klucza w minterze); nie zostały pobrane z korpusu RAG dla tej strony. Wymóg czasu stałego z ASVS §11.2.4 wiąże implementację repozytorium dostarczaną przez operatora, a nie samą klasę uwierzytelniacza.
Uwagi programistyczne
Dział zatytułowany „Uwagi programistyczne”- Dostarcz trwałe implementacje
ApiKeyRepositoryInterfaceorazStripeAdapterInterface; pakiet dostarcza kontrakty i klienta dostawcy PSR-18, a nie warstwę utrwalania. - Zależności to wyłącznie abstrakcje PSR: logger PSR-3, klient HTTP PSR-18, fabryki żądań i strumieni PSR-17. Nie jest wymagany żaden SDK dostawcy.
- Uruchamiaj synchronizację pomiarów jako zaplanowane zadanie. Utrwalaj zwrócone znaczniki wodne trwale po każdym cyklu.
- Uwidocznij klientom procent ostrzeżenia limitu, na przykład jako nagłówek ostrzeżenia, i deduplikuj alerty limitu w obrębie okresu w wywołaniu zwrotnym.
- Dostarcz sekret mintera tokenów z konfiguracji jako losową wartość o wysokiej entropii; zalecane 32 lub więcej losowych bajtów. Nigdy nie wyprowadzaj go z hasła.
- Prefiksy kluczy czynią środowisko widocznym bez wyszukiwania; klucze piaskownicy i produkcyjne nigdy nie kolidują, ponieważ prefiks uczestniczy w przechowywanym skrócie.
- Szczegóły wewnętrznego mechanizmu pozostają w wewnętrznej dokumentacji repozytorium źródłowego i są poza zakresem tego podręcznika.
Granica publikacji
Dział zatytułowany „Granica publikacji”Ta strona dokumentuje wyłącznie zewnętrznie obserwowalne zachowanie oraz wspieraną publiczną powierzchnię API. Wewnętrzne ścieżki przestrzeni nazw, klasy pomocnicze, tabele mechanizmów, nazwy plików runbooków oraz prefiksy zgłoszeń są poza zakresem.