Ga naar inhoud
getnextpdf.com

Enterprise editie

SaaS — Diepe referentie

De Enterprise SaaS-module levert de multi-tenant bouwstenen voor een op NextPDF gebaseerde service.

  • TenantContext is een onveranderlijk identity-value-object dat uitsluitend uit geauthenticeerde context wordt geresolveerd.
  • ApiKeyGenerator en ApiKeyAuthenticator geven API-keys met prefix, checksum en hash-opslag uit en valideren ze.
  • QuotaChecker bewaakt verzoeken tegen quota per tenant: waarschuwen bij 80%, afwijzen bij 100%, fail-closed weigeren wanneer het verbruik onbekend is.
  • SidecarJwtMinter genereert kortlevende HS256-servicetokens voor aanroepen tussen componenten.
  • UsageMeter en StripeMeteringSyncer halen verbruiksgebeurtenissen op en synchroniseren ze met deterministische idempotentie naar de billing provider.

Deze mogelijkheid wordt geleverd in NextPDF Enterprise (nextpdf/enterprise) en wordt geactiveerd met een licentie-envelop op Enterprise-niveau. Een implementatie zonder dat recht laadt de klassen van de mogelijkheid niet. Vergelijk edities en vraag een licentie aan.

Het SaaS-oppervlak is een basismogelijkheid van Enterprise; er bestaat geen aparte vlag per functie. NextPDF Core (Apache-2.0) en NextPDF Pro hebben geen tenancy-, API-key- of quotamodel; deze mogelijkheid heeft geen equivalent op een lager niveau.

Terminal window
composer require nextpdf/enterprise:^3

Alle symbolen bevinden zich onder NextPDF\Enterprise\SaaS.

SymboolParametersStandaardgedragRetourneertWerpt of faalt metOpmerkingen
TenantContextstring $tenantId, string $source, array $scopes = ['read']Onveranderlijk identity-value-objectvalue-objectNietsBronnen: jwt, mtls, api_key; hasScope() / hasAnyScope() testen scopes
TenantContext::singleTenant()geenVaste default-tenant met read, write, adminTenantContextNietsSingle-tenant-implementaties
ApiKeyAuthenticator::authenticate()string $rawKeyValidatie in zes stappen, daarna contextresolutieTenantContextApiKeyAuthenticationException (HTTP 401)Context-source is api_key; scopes gekopieerd uit het key-record
ApiKeyAuthenticator::requireScope()TenantContext $context, ApiKeyScope $requiredScopeExpliciete scope-assertievoidApiKeyAuthenticationException::insufficientScope() (HTTP 403)Scope-handhaving is een aparte, expliciete stap
ApiKeyGenerator::generateLive() / ::generateTest()geenNieuwe key: prefix, base62-body van 32 tekens (192-bit entropie), checksum van 4 tekensarray{key, hash, prefix}NietsPrefixen npf_live_ / npf_test_; hash is de opslagdigest
ApiKeyGenerator::validateChecksum()string $keyVormcontrole op prefix, lengte en CRC32-checksumboolNietsTypefoutbescherming vóór elke datastore-lookup; geen beveiligingscontrole
ApiKeyGenerator::hashKey() (static)string $keySHA-256-hexdigest van de ruwe keystringNietsDe enige opgeslagen representatie van een key
ApiKeyGenerator::isLiveKey() / ::isTestKey()string $keyPrefixinspectieboolNietsOmgeving zichtbaar zonder lookup
ApiKeyid, tenant, key-hash, display-prefix, scope-mask, aangemaakt-/verval-/intrekmomentenOpgeslagen key-record; plaintext wordt nooit bewaardvalue-objectNietsisActive(), isRevoked(), isExpired(), scopeNames()
ApiKeyScopebacked enum: Read = 1, Write = 2, Admin = 4Bitmask-scopemodelenumNietsmaskFromNames(), fromName(), fullAccess(); onbekende namen worden door de mask-builder genegeerd
ApiKeyRepositoryInterfaceOpslagcontract; alleen hash-persistentieImplementatie-afhankelijkfindByHash(), findActiveByTenant(), store(), revoke()
SidecarJwtMinter::__construct()string $secret, issuer, audience, int $ttlSeconds = 300Weigert een signing-secret onder 16 bytes bij constructieinstanceInvalidArgumentExceptionMinimale key-sterkte van 128-bit; 32 of meer willekeurige bytes aanbevolen
SidecarJwtMinter::mint()TenantContext $tenantHS256-JWT met iss, aud, sub, scope, tenant_id, iat, exp, jtistringJsonException bij fout in claim-encodingStandaard levensduur van vijf minuten; jti is 16 willekeurige bytes, hex-gecodeerd
QuotaChecker::check()TenantContext $tenant, TenantQuota $quotaLeest het huidige verbruik; waarschuwt bij 80%; wijst af bij 100%; weigert wanneer het verbruik onbekend isarray{allowed: bool, warning_percentage: float|null}QuotaExceededException, QuotaUnavailableExceptionAlert-callback aangeroepen bij beide drempels
TenantQuotafloat $maxCuPerPeriod, collections, storage bytes, concurrent jobsLimieten per periode; softdrempelconstante van 80%value-objectNietsfromConfig()-standaarden: 10,000 CU, 100 collections, 10 GB, 10 jobs
QuotaExceededException::toErrorEnvelope()geenSPEC-QUOTA-001-foutenveloparrayHTTP 402, niet herhaalbaar; draagt huidige waarde, limiet en resetmoment
QuotaUnavailableException::toErrorEnvelope()geenSPEC-QUOTA-503-foutenveloparrayHTTP 503, herhaalbaar; reden usage_undeterminable
UsageMeter::pullUsage()array<string, int> $watermarksPollt elke geconfigureerde usage-source-host vanaf zijn cursorarray{events, instance_id}UsageMeterException wanneer elke host onbereikbaar isGedeeltelijke uitval wordt getolereerd; onbereikbare hosts worden gelogd en overgeslagen
UsageMeter::getCurrentUsage()string $tenantIdCompute-unit-verbruik van de huidige periodefloatUsageMeterException wanneer het verbruik onbepaalbaar isEen parseerbare nul is gezaghebbend; onbekend verbruik werpt een exception
StripeMeteringSyncer::sync()array<string, int> $watermarksEén cyclus van pull, transform en sendarray{watermarks, sent, failed}Niets; send-fouten worden naar de DLQ-callback gerouteerdEen pull-fout retourneert een no-op-cyclus die de cursor behoudt
StripeAdapter::sendMeterEvent()MeterEvent $eventPOST naar de provider met een idempotency-headervoidStripeSyncExceptionHTTP 429 en 5xx herhaalbaar; overige 4xx niet herhaalbaar
StripeAdapter::sendBatch()list<MeterEvent> $eventsVerstuurt elke event; verzamelt foutenlist<StripeSyncException>NietsEen lege lijst betekent dat elke event is geslaagd
MeterEventmeter-naam, tenant, waarde, idempotency-key, timestampOnveranderlijk meter-event-value-objectvalue-objectNietstoStripePayload() serialiseert de provider-payload
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 {}
}
  • Tenantidentiteit. Een tenantcontext is onveranderlijk: tenant-identificator, resolutiebron, scopes. Identiteit wordt uitsluitend uit geauthenticeerde context geresolveerd (jwt, mtls, api_key) — nooit uit een door de client aangeleverde header of queryparameter. Een single-tenant-implementatie gebruikt de vaste default-context met volledige scopes.
  • Authenticatievolgorde. API-key-authenticatie verloopt in een vaste volgorde: checksum, SHA-256-hash, repository-lookup, intrekkingscontrole, vervalcontrole, contextresolutie. Onbekende, ingetrokken en verlopen keys zijn drie afzonderlijke uitkomsten, alle HTTP 401; onvoldoende scope is HTTP 403.
  • Key-geheimhouding. De ruwe key wordt nooit opgeslagen of gelogd; alleen de SHA-256-digest ervan wordt bewaard en opgezocht. De authenticator voert zelf geen byte-gewijze secret-vergelijking uit; een timing-safe digest-lookup is het contract van de repository-implementatie.
  • Quotadrempels. Bij de softlimiet van 80% gaat het verzoek door, wordt het waarschuwingspercentage geretourneerd en vuurt de alert-callback. Bij de hardlimiet van 100% wordt het verzoek afgewezen met SPEC-QUOTA-001 (HTTP 402), die het resetmoment draagt — de eerste dag van de volgende maand, middernacht UTC.
  • Quota fail-closed. Onbepaalbaar verbruik wijst het verzoek af met SPEC-QUOTA-503 (HTTP 503, herhaalbaar). Onbekend verbruik wordt nooit als nul behandeld. Een echt, parseerbaar nulverbruik is gezaghebbend en wordt toegelaten.
  • Alert-deduplicatie. De checker dedupliceert alerts niet; deduplicatie per periode is de verantwoordelijkheid van de callback.
  • Metering-sync. De cyclus is gepland, nooit op het verzoekpad. Hij hervat vanaf watermarks per bron en schuift elke cursor door naar de hoogste succesvol verstuurde gebeurtenisidentiteit. De idempotency-key is deterministisch — tenant, periode, gebeurtenisidentiteit — zodat een opnieuw verstuurde gebeurtenis samenvalt door de deduplicatie aan providerzijde.
  • Pull-fout. Een mislukte pull retourneert een no-op-cyclus (sent 0, failed 0) die de watermarks behoudt; de volgende cyclus probeert hetzelfde venster opnieuw in plaats van het over te slaan.
  • Servicetokens. Tokens zijn HS256 met een gedeeld secret en dragen iss, aud, sub, scope, tenant_id, iat, exp en een uniek jti. De standaard levensduur is vijf minuten. De constructie weigert een secret onder 16 bytes, fail-closed.
  • Een misvormde key faalt de checksum en wordt afgewezen vóór elke datastore-toegang. Een goedgevormde maar onbekende key wordt afgewezen na lookup. Beide verschijnen als de ongeldige-key-uitkomst.
  • Onbekende, ingetrokken en verlopen keys gebruiken afzonderlijke exception-factory’s; de keyExpired-vlag is alleen waar bij de verlopen-uitkomst. Map ze naar afzonderlijke clientantwoorden.
  • QuotaChecker::check() keert alleen terug bij toelating; de geretourneerde allowed is altijd true. Afwijzing en onbeschikbaarheid zijn exceptionele uitkomsten.
  • TenantQuota::usagePercentage() retourneert 0.0 bij een niet-positief quotum; fromConfig() vult standaarden in voor ontbrekende waarden en klemt integer-limieten tot minimaal 1.
  • Watermarks zijn per bron; een ontbrekende watermark start vanaf het begin van de stream van die bron (cursor 0). Een multi-source-implementatie houdt onafhankelijke watermarks bij.
  • De transform slaat niet-array-gebeurtenissen over, gebeurtenissen met een ontbrekende of lege operatie of tenant, een niet-positieve waarde, of een ongemapte operatie — zonder de cyclus te laten falen. Een gebeurtenis zonder bruikbare positieve integer-identiteit wordt geweigerd met een waarschuwing: een willekeurige fallback-key zou de deduplicatie aan providerzijde ondermijnen en de tenant dubbel kunnen factureren.
  • Tien opeenvolgende send-fouten escaleren naar een kritieke logregel; de teller reset bij elke geslaagde send. Elke mislukte gebeurtenis bereikt nog steeds de dead-letter-callback.
  • Een misvormde JSON-body van een usage-source-host levert een lege gebeurtenislijst op, geen cyclusfout. pullUsage() werpt alleen wanneer elke geconfigureerde host onbereikbaar is.
  • Digest- en MAC-primitieven zijn SHA-256 en HMAC-SHA256 via de crypto-provider van de host-PHP. Een FIPS-beperkte build gaat fail-closed bij een niet-goedgekeurd algoritme in plaats van te degraderen; de SaaS-laag voegt geen eigen cryptografisch beleid toe.
  • Key-bodies en token-identificatoren komen uit de CSPRNG (random_int(), random_bytes()).
  • De CRC32-checksum is geen cryptografische controle en wordt niet beïnvloed door de FIPS-modus.

De onderstaande uitspraken beschrijven de mogelijkheid ten opzichte van de geciteerde clausules. Het zijn geen certificeringsclaims; NextPDF houdt geen certificering voor deze module.

GedragReferentie
Not-after-semantiek van service-token expRFC 7519 §4.1.4
JWS compact serialization van service-tokenRFC 7515 §3.1
Minimale HS256-secret van 16 bytes; geen door mensen te onthouden wachtwoorden als MAC-keysRFC 8725 §3.5 (threat: §2.2)
Constant-time-contract voor repository-digest-lookupOWASP ASVS 5.0 §11.2.4
Opslagdigest van API-key SHA-256FIPS 180-4 (code-declared)

De citaten van RFC 8725 en OWASP ASVS 5.0 zijn RAG-verified; volledige reference-identifiers zijn vastgelegd in de frontmatter van deze pagina. De referenties naar FIPS 180-4, FIPS 198-1 en BSI TR-02102-1 zijn code-declared in de productbron (hash('sha256', …) en de gedocumenteerde key-ondergrens van de minter); ze zijn voor deze pagina niet uit het RAG-corpus opgehaald. De constant-time-eis van ASVS §11.2.4 bindt de repository-implementatie die de operator levert, niet de authenticator-klasse zelf.

  • Lever duurzame implementaties van ApiKeyRepositoryInterface en StripeAdapterInterface; het pakket levert de contracten en een PSR-18-provider-client, geen persistentie.
  • Afhankelijkheden zijn uitsluitend PSR-abstracties: PSR-3-logger, PSR-18-HTTP-client, PSR-17-request- en -stream-factory’s. Er is geen provider-SDK vereist.
  • Draai de metering-sync als een geplande job. Bewaar de geretourneerde watermarks duurzaam na elke cyclus.
  • Toon het quota-waarschuwingspercentage aan clients, bijvoorbeeld als waarschuwingsheader, en dedupliceer quota-alerts per periode in de callback.
  • Lever het secret van de token-minter vanuit configuratie als een willekeurige waarde met hoge entropie; 32 of meer willekeurige bytes wordt aanbevolen. Leid het nooit af uit een wachtwoord.
  • Key-prefixen maken de omgeving zichtbaar zonder lookup; sandbox- en production-keys botsen nooit omdat de prefix deel uitmaakt van de opgeslagen digest.
  • Interne mechanismedetails blijven in de interne documentatie van de bronrepository en vallen buiten de scope van deze handleiding.

Deze pagina documenteert alleen extern waarneembaar gedrag en het ondersteunde publieke API-oppervlak. Interne namespace-paden, helper-klassen, mechanismetabellen, runbook-bestandsnamen en ticketprefixen vallen buiten de scope.