Zum Inhalt springen
getnextpdf.com

Enterprise Edition

SaaS — Ausführliche Referenz

Das Enterprise-SaaS-Modul liefert die mandantenfähigen Bausteine für einen NextPDF-basierten Dienst.

  • TenantContext ist ein unveränderliches Identitäts-Wertobjekt, das ausschließlich aus authentifiziertem Kontext aufgelöst wird.
  • ApiKeyGenerator und ApiKeyAuthenticator stellen präfixierte, prüfsummenbehaftete, hash-gespeicherte API-Keys aus und validieren sie.
  • QuotaChecker reglementiert Anfragen anhand mandantenspezifischer Kontingente: Warnung bei 80 %, Ablehnung bei 100 %, Fail-closed-Verweigerung, wenn die Nutzung unbekannt ist.
  • SidecarJwtMinter prägt kurzlebige HS256-Service-Tokens für Aufrufe zwischen Komponenten.
  • UsageMeter und StripeMeteringSyncer ziehen Nutzungsereignisse und synchronisieren sie mit deterministischer Idempotenz zum Abrechnungsanbieter.

Diese Funktion wird in NextPDF Enterprise (nextpdf/enterprise) ausgeliefert und aktiviert sich mit einer Lizenz-Envelope der Enterprise-Stufe. Ein Deployment ohne diese Berechtigung lädt die Klassen der Funktion nicht. Editionen vergleichen und eine Lizenz erwerben.

Die SaaS-Oberfläche ist eine Enterprise-Basisfunktion; ein separates Feature-Flag existiert nicht. NextPDF Core (Apache-2.0) und NextPDF Pro haben kein Mandanten-, API-Key- oder Kontingentmodell; für diese Funktion gibt es kein Äquivalent in einer niedrigeren Stufe.

Terminal-Fenster
composer require nextpdf/enterprise:^3

Alle Symbole liegen unter NextPDF\Enterprise\SaaS.

SymbolParameterStandardverhaltenRückgabeWirft oder scheitert mitHinweise
TenantContextstring $tenantId, string $source, array $scopes = ['read']Unveränderliches Identitäts-WertobjektWertobjektNichtsQuellen: jwt, mtls, api_key; hasScope() / hasAnyScope() prüfen Scopes
TenantContext::singleTenant()keineFester default-Mandant mit read, write, adminTenantContextNichtsEinzelmandanten-Deployments
ApiKeyAuthenticator::authenticate()string $rawKeySechsstufige Validierung, dann KontextauflösungTenantContextApiKeyAuthenticationException (HTTP 401)Kontext-source ist api_key; Scopes werden aus dem Key-Datensatz kopiert
ApiKeyAuthenticator::requireScope()TenantContext $context, ApiKeyScope $requiredScopeExplizite Scope-ZusicherungvoidApiKeyAuthenticationException::insufficientScope() (HTTP 403)Die Scope-Durchsetzung ist ein separater, expliziter Schritt
ApiKeyGenerator::generateLive() / ::generateTest()keineNeuer Key: Präfix, 32-Zeichen-base62-Körper (192-Bit-Entropie), 4-Zeichen-Prüfsummearray{key, hash, prefix}NichtsPräfixe npf_live_ / npf_test_; hash ist der Speicher-Digest
ApiKeyGenerator::validateChecksum()string $keyPrüfung von Präfix, Länge und CRC32-PrüfsummenformboolNichtsTippfehlerschutz vor jedem Datenspeicher-Lookup; keine Sicherheitskontrolle
ApiKeyGenerator::hashKey() (static)string $keySHA-256-Hex-Digest des Roh-KeysstringNichtsDie einzige gespeicherte Repräsentation eines Keys
ApiKeyGenerator::isLiveKey() / ::isTestKey()string $keyPräfixprüfungboolNichtsUmgebung ohne Lookup sichtbar
ApiKeyID, Mandant, Key-Hash, Anzeige-Präfix, Scope-Maske, Erstellungs-/Ablauf-/WiderrufszeitpunkteGespeicherter Key-Datensatz; Klartext wird nie persistiertWertobjektNichtsisActive(), isRevoked(), isExpired(), scopeNames()
ApiKeyScopeBacked-Enum: Read = 1, Write = 2, Admin = 4Bitmasken-Scope-ModellEnumNichtsmaskFromNames(), fromName(), fullAccess(); unbekannte Namen werden vom Maskenersteller ignoriert
ApiKeyRepositoryInterfaceSpeichervertrag; ausschließlich Hash-PersistenzImplementierungsabhängigfindByHash(), findActiveByTenant(), store(), revoke()
SidecarJwtMinter::__construct()string $secret, Issuer, Audience, int $ttlSeconds = 300Weist bei der Konstruktion ein Signatur-Secret unter 16 Byte zurückInstanzInvalidArgumentException128-Bit-Mindestschlüsselstärke; 32 oder mehr Zufallsbyte empfohlen
SidecarJwtMinter::mint()TenantContext $tenantHS256-JWT mit iss, aud, sub, scope, tenant_id, iat, exp, jtistringJsonException bei Fehler in der Claim-KodierungFünf Minuten Standardlebensdauer; jti besteht aus 16 Zufallsbyte, hex-kodiert
QuotaChecker::check()TenantContext $tenant, TenantQuota $quotaLiest die aktuelle Nutzung; warnt bei 80 %; lehnt bei 100 % ab; verweigert, wenn die Nutzung unbekannt istarray{allowed: bool, warning_percentage: float|null}QuotaExceededException, QuotaUnavailableExceptionAlert-Callback wird an beiden Schwellen aufgerufen
TenantQuotafloat $maxCuPerPeriod, Collections, Speicher-Byte, gleichzeitige JobsLimits pro Periode; 80-%-Soft-SchwellenkonstanteWertobjektNichtsfromConfig()-Standardwerte: 10,000 CU, 100 Collections, 10 GB, 10 Jobs
QuotaExceededException::toErrorEnvelope()keineSPEC-QUOTA-001-Fehler-EnvelopearrayHTTP 402, nicht wiederholbar; enthält aktuellen Wert, Limit und Reset-Zeitpunkt
QuotaUnavailableException::toErrorEnvelope()keineSPEC-QUOTA-503-Fehler-EnvelopearrayHTTP 503, wiederholbar; Grund usage_undeterminable
UsageMeter::pullUsage()array<string, int> $watermarksFragt jeden konfigurierten Usage-Source-Host ab seinem Cursor abarray{events, instance_id}UsageMeterException, wenn jeder Host unerreichbar istTeilausfall toleriert; unerreichbare Hosts werden protokolliert und übersprungen
UsageMeter::getCurrentUsage()string $tenantIdCompute-Unit-Nutzung der aktuellen PeriodefloatUsageMeterException, wenn die Nutzung unbestimmbar istEine parsbare Null ist maßgeblich; unbekannte Nutzung wirft
StripeMeteringSyncer::sync()array<string, int> $watermarksEin Pull-, Transform-, Send-Zyklusarray{watermarks, sent, failed}Nichts; Sendefehler werden an den DLQ-Callback geleitetEin Pull-Fehler liefert einen No-op-Zyklus, der den Cursor bewahrt
StripeAdapter::sendMeterEvent()MeterEvent $eventPOST an den Provider mit einem Idempotenz-HeadervoidStripeSyncExceptionHTTP 429 und 5xx wiederholbar; andere 4xx nicht wiederholbar
StripeAdapter::sendBatch()list<MeterEvent> $eventsSendet jedes Event; sammelt Fehlerlist<StripeSyncException>NichtsEine leere Liste bedeutet, dass jedes Event erfolgreich war
MeterEventMeter-Name, Mandant, Wert, Idempotenzschlüssel, ZeitstempelUnveränderliches Meter-Event-WertobjektWertobjektNichtstoStripePayload() serialisiert die 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 {}
}
  • Mandantenidentität. Ein Mandantenkontext ist unveränderlich: Mandantenkennung, Auflösungsquelle, Scopes. Die Identität wird ausschließlich aus authentifiziertem Kontext aufgelöst (jwt, mtls, api_key) — niemals aus einem vom Client gelieferten Header oder Query-Parameter. Ein Einzelmandanten-Deployment verwendet den festen default-Kontext mit vollen Scopes.
  • Authentifizierungsreihenfolge. Die API-Key-Authentifizierung läuft in fester Reihenfolge ab: Prüfsumme, SHA-256-Hash, Repository-Lookup, Widerrufsprüfung, Ablaufprüfung, Kontextauflösung. Unbekannte, widerrufene und abgelaufene Keys sind drei verschiedene Ergebnisse, alle HTTP 401; unzureichender Scope ist HTTP 403.
  • Key-Geheimhaltung. Der Roh-Key wird nie gespeichert oder protokolliert; nur sein SHA-256-Digest wird persistiert und nachgeschlagen. Der Authenticator führt selbst keinen byteweisen Geheimnisvergleich durch; der konstantzeitige Digest-Lookup ist Vertragsbestandteil der Repository-Implementierung.
  • Kontingentschwellen. Beim 80-%-Soft-Limit läuft die Anfrage weiter, der Warnprozentsatz wird zurückgegeben und der Alert-Callback wird ausgelöst. Beim 100-%-Hard-Limit wird die Anfrage mit SPEC-QUOTA-001 (HTTP 402) abgelehnt und trägt den Reset-Zeitpunkt — den ersten Tag des Folgemonats, Mitternacht UTC.
  • Kontingent Fail-closed. Unbestimmbare Nutzung verweigert die Anfrage mit SPEC-QUOTA-503 (HTTP 503, wiederholbar). Unbekannte Nutzung wird nie als Null behandelt. Eine echte, parsbare Nullnutzung ist maßgeblich und lässt zu.
  • Alert-Deduplizierung. Der Checker dedupliziert keine Alerts; die Deduplizierung pro Periode liegt in der Verantwortung des Callbacks.
  • Metering-Synchronisation. Der Zyklus ist geplant, niemals auf dem Anfragepfad. Er setzt bei quellenspezifischen Watermarks wieder ein und rückt jeden Cursor auf die höchste erfolgreich gesendete Ereignisidentität vor. Der Idempotenzschlüssel ist deterministisch — Mandant, Periode, Ereignisidentität —, sodass ein erneut gesendetes Event bei der Deduplizierung des Providers kollabiert.
  • Pull-Fehler. Ein fehlgeschlagener Pull liefert einen No-op-Zyklus (sent 0, failed 0), der die Watermarks bewahrt; der nächste Zyklus wiederholt dasselbe Fenster, anstatt es zu überspringen.
  • Service-Tokens. Tokens sind HS256 mit einem gemeinsamen Secret und tragen iss, aud, sub, scope, tenant_id, iat, exp sowie ein eindeutiges jti. Die Standardlebensdauer beträgt fünf Minuten. Die Konstruktion weist ein Secret unter 16 Byte zurück, fail-closed.
  • Ein fehlerhaft geformter Key scheitert an der Prüfsumme und wird vor jedem Datenspeicherzugriff abgelehnt. Ein wohlgeformter, aber unbekannter Key wird nach dem Lookup abgelehnt. Beide erscheinen als Ungültiger-Key-Ergebnis.
  • Unbekannte, widerrufene und abgelaufene Keys verwenden verschiedene Exception-Factories; das Flag keyExpired ist nur beim Ablauf-Ergebnis wahr. Bilden Sie sie auf verschiedene Client-Antworten ab.
  • QuotaChecker::check() kehrt nur bei Zulassung zurück; das zurückgegebene allowed ist immer true. Ablehnung und Nichtverfügbarkeit sind Ausnahmeergebnisse.
  • TenantQuota::usagePercentage() liefert 0.0 für ein nicht-positives Kontingent; fromConfig() ersetzt fehlende Werte durch Standardwerte und begrenzt Ganzzahllimits auf mindestens 1.
  • Watermarks sind quellenspezifisch; ein fehlendes Watermark beginnt am Anfang des Streams dieser Quelle (Cursor 0). Ein Multi-Source-Deployment führt unabhängige Watermarks.
  • Die Transformation überspringt Nicht-Array-Events, Events mit fehlender oder leerer Operation oder fehlendem Mandanten, mit nicht-positivem Wert oder einer nicht zugeordneten Operation — ohne den Zyklus scheitern zu lassen. Ein Event ohne verwendbare positive Ganzzahlidentität wird mit einer Warnung abgelehnt: Ein zufälliger Fallback-Schlüssel würde die providerseitige Deduplizierung aushebeln und könnte den Mandanten doppelt berechnen.
  • Zehn aufeinanderfolgende Sendefehler eskalieren zu einem kritischen Log-Eintrag; der Zähler wird bei jedem erfolgreichen Senden zurückgesetzt. Jedes fehlgeschlagene Event erreicht dennoch den Dead-Letter-Callback.
  • Ein fehlerhafter JSON-Body von einem Usage-Source-Host ergibt eine leere Ereignisliste, keinen Zyklusfehler. pullUsage() wirft nur, wenn jeder konfigurierte Host unerreichbar ist.
  • Digest- und MAC-Primitive sind SHA-256 und HMAC-SHA256 über den PHP-Krypto-Provider des Hosts. Ein FIPS-beschränkter Build scheitert bei einem nicht zugelassenen Algorithmus fail-closed, statt herabzustufen; die SaaS-Schicht fügt keine eigene Kryptografie-Richtlinie hinzu.
  • Key-Körper und Token-Bezeichner stammen aus dem CSPRNG (random_int(), random_bytes()).
  • Die CRC32-Prüfsumme ist keine kryptografische Kontrolle und wird vom FIPS-Modus nicht beeinflusst.

Die folgenden Aussagen beschreiben die Fähigkeit gegenüber den zitierten Klauseln. Sie sind keine Zertifizierungsansprüche; NextPDF besitzt keine Zertifizierung für dieses Modul.

VerhaltenReferenz
Service-Token-exp-Not-After-SemantikRFC 7519 §4.1.4
Service-Token-JWS-Compact-SerialisierungRFC 7515 §3.1
16-Byte-Mindest-HS256-Secret; keine menschlich merkbaren Passwörter als MAC-SchlüsselRFC 8725 §3.5 (Bedrohung: §2.2)
Konstantzeit-Vertrag für Repository-Digest-LookupOWASP ASVS 5.0 §11.2.4
API-Key-Speicher-Digest SHA-256FIPS 180-4 (im Code deklariert)

Die Zitate zu RFC 8725 und OWASP ASVS 5.0 sind RAG-verifiziert; die vollständigen Referenzkennungen sind im Frontmatter dieser Seite hinterlegt. Die Referenzen zu FIPS 180-4, FIPS 198-1 und BSI TR-02102-1 sind im Produktquellcode deklariert (hash('sha256', …) und die dokumentierte Schlüsseluntergrenze des Minters); sie wurden für diese Seite nicht aus dem RAG-Korpus abgerufen. Die Konstantzeit-Anforderung von ASVS §11.2.4 bindet die vom Betreiber bereitgestellte Repository-Implementierung, nicht die Authenticator-Klasse selbst.

  • Stellen Sie dauerhafte Implementierungen von ApiKeyRepositoryInterface und StripeAdapterInterface bereit; das Paket liefert die Verträge und einen PSR-18-Provider-Client, keine Persistenz.
  • Abhängigkeiten sind ausschließlich PSR-Abstraktionen: PSR-3-Logger, PSR-18-HTTP-Client, PSR-17-Request- und Stream-Factories. Kein Provider-SDK ist erforderlich.
  • Führen Sie die Metering-Synchronisation als geplanten Job aus. Persistieren Sie die zurückgegebenen Watermarks nach jedem Zyklus dauerhaft.
  • Geben Sie den Kontingent-Warnprozentsatz an Clients weiter, etwa als Warn-Header, und deduplizieren Sie Kontingent-Alerts pro Periode im Callback.
  • Liefern Sie das Token-Minter-Secret aus der Konfiguration als hochentropischen Zufallswert; 32 oder mehr Zufallsbyte werden empfohlen. Leiten Sie es niemals aus einem Passwort ab.
  • Key-Präfixe machen die Umgebung ohne Lookup sichtbar; Sandbox- und Produktions-Keys kollidieren nie, weil das Präfix in den gespeicherten Digest eingeht.
  • Interne Mechanismusdetails verbleiben in der internen Dokumentation des Quell-Repositorys und liegen außerhalb des Geltungsbereichs dieses Handbuchs.

Diese Seite dokumentiert ausschließlich extern beobachtbares Verhalten und die unterstützte öffentliche API-Oberfläche. Interne Namespace-Pfade, Hilfsklassen, Mechanismustabellen, Runbook-Dateinamen und Ticket-Präfixe liegen außerhalb des Geltungsbereichs.