Enterprise 版本
Licensing — 深入參考
快速一覽
標題為「快速一覽」的區段本頁是 NextPDF Enterprise 授權介面的深入參考——這個模組會解析所有其他 Enterprise 能力。它涵蓋權益狀態模型(EntitlementEvaluator、EntitlementStatus、EntitlementResult)、強制執行的能力閘控(FeatureGate、CapabilityCode、CapabilityRegistry)、雙計時器通道強制執行機器(ChannelEnforcementPolicy、OnlineCheckScheduler),以及已簽署的線上用戶端(LicenseClient)。一般的權益評估不需要任何網路。線上用戶端的存在是為了明確的 activate、heartbeat、deactivate、verify-online 與續約探測操作。內部機制細節留在原始碼儲存庫的內部文件中,不在本手冊的範圍內。
可用性與授權
標題為「可用性與授權」的區段此能力隨 NextPDF Enterprise(nextpdf/enterprise)提供,並在 Enterprise 層級的授權封套下啟用。不具該權益的部署不會載入此能力的類別。比較版本並取得授權。
Licensing 是一個基礎 Enterprise 介面。只要 Enterprise 套件與 Core 套件並列安裝,它就一定存在,因為它是解析其他一切的那個介面。NextPDF Pro 沒有獨立的權益介面;狀態模型、強制執行機器與線上用戶端僅隨 nextpdf/enterprise 提供。NextPDF Core(Apache-2.0)完全沒有權益模型——需要未授權 Apache-2.0 行為的主機會直接使用 Core 引擎,而非 Enterprise 管線。
公開 API 介面
標題為「公開 API 介面」的區段所有型別都位於 NextPDF\Enterprise\Licensing 之下。強制執行機器位於 Channel 子命名空間、執行階段組合位於 Runtime,而已簽署狀態型別位於 Online。
| 符號 | 參數 | 預設行為 | 回傳 | 拋出或失敗於 | 備註 |
|---|---|---|---|---|---|
EntitlementEvaluator::evaluate() | ?LicenseKey $license, ?DateTimeImmutable $now = null | 將一份已驗證的授權(或 null)對應到完整的權益決定 | EntitlementResult | 無 | null 授權會產生 fail-closed 的 no-license 結果 |
EntitlementEvaluator::evaluateEnforced() | ?LicenseKey $license, EnforcementOutcome $outcome | 將一個預先算出的強制執行結果轉換為權益結果 | EntitlementResult | 無 | 生效版本來自該結果;每一次強制執行降級都落在 Core |
EntitlementEvaluator::resolveBrandingMode(), ::resolveBrandingStrategy() | ?LicenseKey $license, ?DateTimeImmutable $now = null | 為所產生的輸出解析品牌標示決定 | BrandingMode / BrandingStrategy | 無 | 唯一的品牌標示權威;只有評估通道會加浮水印 |
EntitlementResult | 唯讀值物件 | 承載狀態、版本、通道、品牌標示模式、執行階段/更新/支援旗標,以及一個可選的警告 | — | 無 | 輔助方法:isFullyActive(), isExpired(), isDowngradedToCore(), shouldApplyEvaluationBranding() |
EntitlementStatus | 字串支援的 enum | Active, GracePeriod, DowngradedToCore, EvaluationExpired, NoLicense | — | 無 | PerpetualFallback 保留但已棄用;僅為讓 6.0 之前的消費端仍能編譯而保留 |
FeatureGate::hasCapability() | CapabilityCode $capability | 對照強制執行的生效版本與套件包狀態來檢查該能力 | bool | 無 | 任何解析器錯誤都會 fail-closed 到 core |
FeatureGate::requireCapability() | CapabilityCode $capability | 靜默通過,或以結構化的加售情境拋出 | void | SpectrumAuthenticationException SPEC-LIC-001(未授權)或 SPEC-LIC-002(套件包已過期) | 該情境承載 required_capability, required_pack, current_packs, upgrade_info_url |
FeatureGate::isFeatureEnabled(), ::requireFeature() | string $feature | 舊版功能識別碼檢查 | bool / void | SpectrumAuthenticationException SPEC-AUTH-007(require 路徑) | 舊版相容;建議改用能力檢查 |
FeatureGate 存取器 | 無 | 授權自省 | 視情況而定 | 無 | currentEdition(), isLicenseValid(), isInGracePeriod(), getActivePacks(), trialPolicy() |
CapabilityCode | 字串支援的 enum | 正規能力識別碼(core.*, pro.*, enterprise.*, pack.*) | — | 無 | isPack(), packName(), minimumEdition(), forEdition() |
CapabilityRegistry | LicenseKey, route patterns | 派送前的路由到能力解析與套件包查找 | 視情況而定 | 無 | registerDefaultRoutes() 涵蓋已記載的服務路由 |
LicenseKey | 唯讀值物件 | 已解析、已驗證的授權 claims | — | 無 | isExpired(), isInGracePeriod(), isFullyExpired(), hasFeature(), hasCapability(), activePackNames();maxSlots 僅為合約性質 |
LicenseValidator::buildVerifiedLicenseKey() | array $data | 從一份已驗證的酬載建立一個有型別的 LicenseKey | LicenseKey | schema 違規時拋出 SpectrumAuthenticationException SPEC-AUTH-001 | 支援 schema 2.0、1.0 與舊版酬載 |
PackDefinition / PackRegistry | 唯讀值物件 | 具獨立到期與寬限的附加套件包狀態 | 視情況而定 | 無 | isActive(), isFullyExpired(), activePacks(), activeCapabilities() |
TrialPolicy | bool $enabled, int $maxPagesPerSecond = 2, watermark fields | 試用吞吐量上限原則 | — | 無 | fromLicenseKey(), disabled(), isThrottled();品牌標示權威仍歸評估器 |
ChannelEnforcementPolicy::enforce() | LicenseKey $license, LocalState $state, int $systemNow, bool $revoked = false | 執行雙計時器機器,外加撤銷與試用重新標記 | EnforcementOutcome | 無 | 撤銷會凌駕每個計時器與試用狀態 |
OnlineCheckScheduler::evaluate(), ::recordSuccessfulOnlineCheck() | License, local state, timestamps | 雙計時器評估;以已驗證的伺服器時間續租 | EnforcementOutcome / LocalState | 無 | ONLINE_GRACE_SECONDS 在租約之後給予 48 小時的活躍寬限 |
EnforcementDecision / EnforcementReason / EnforcementOutcome | Enum 加唯讀值物件 | Active, Grace, DowngradedToCore 加上精確的原因 | — | 無 | 每個原因都確定性地對應到一個決定 |
Runtime\EntitlementResolver::currentEntitlement() | 無 | 介面:執行階段所提供的、強制執行後的權益 | EntitlementResult | 由實作定義 | EnforcedEntitlementResolver 組合快取、狀態、原則與評估器,fail-closed 到 Core |
LicenseStateCache::get(), ::getOrThrow() | bool $forceRefresh = false | 支撐閘門的已驗證授權快取 | ?LicenseKey | getOrThrow():SpectrumAuthenticationException SPEC-AUTH-008;接線無效時 LicenseConfigurationException | invalidate(), isCacheValid() |
LicenseClient::activate(), ::heartbeat(), ::deactivate(), ::verifyOnline() | string $licenseJws, string $deploymentId, ?string $machineFingerprintHash = null, ?string $expectedLicenseId = null, ?string $clientNonce = null | 一次已簽署狀態交換;回應會對照釘選的根憑證驗證,並綁定到所送出的 nonce | StatusResponse | LicenseClientException(傳輸、非 200、所提供的 nonce 有誤);SpectrumAuthenticationException(偽造、竄改、過時、nonce 錯誤、授權錯誤、型別錯誤的回應) | 只會送出加鹽的指紋雜湊,絕不送出原始指紋 |
LicenseClient::checkRenewal() | string $licenseId, string $customerId | 續約可用性探測;404 是正常的「沒有可用的」回答 | 續約陣列(available, download_url, expires_at) | LicenseClientException(無法連線、非 200/404,或被原則拒絕的 download_url) | 未簽署主體的 URL 會在能逸出之前先經過 SSRF 篩檢 |
LicenseClient::__construct() | 見簽章區塊 | 在建構時驗證傳輸、URL scheme 與釘選集 | — | LicenseConfigurationException(不強制執行原則的傳輸、不允許的 scheme、無釘選的純 HTTP、格式錯誤的釘選集) | getPinSet() 為輪替工具公開 active 的釘選集 |
Online\StatusResponse | 唯讀值物件 | 已驗證的已簽署狀態 | — | 無 | 在 revoked / expired / released 時 isLive() 為 false;承載 leaseExpiresAt、席次計數與一份權益快照 |
LicenseChannelMode / OnlineMode / OnlinePolicy | Enum 加唯讀值物件 | 通道與線上原則 claims | — | 無 | signed_source 相對於 ioncube;線上模式 required / optional / disabled,附 intervalDays 與 leaseDays |
LicenseEdition | 字串支援的 enum | Core, Pro, Enterprise | — | 無 | satisfies() 實作層級階層 |
public function __construct(private ?EvaluationBrandingConfig $evaluationBrandingConfig = null)public function evaluate(?LicenseKey $license, ?DateTimeImmutable $now = null): EntitlementResultpublic function evaluateEnforced(?LicenseKey $license, EnforcementOutcome $outcome): EntitlementResultpublic function resolveBrandingMode(?LicenseKey $license, ?DateTimeImmutable $now = null): BrandingModepublic function resolveBrandingStrategy(?LicenseKey $license, ?DateTimeImmutable $now = null): BrandingStrategypublic function __construct(private readonly LicenseStateCache $licenseCache, private readonly EntitlementResolver $entitlementResolver, private readonly string $upgradeInfoUrl = self::DEFAULT_UPGRADE_URL)public function isFeatureEnabled(string $feature): boolpublic function requireFeature(string $feature): voidpublic function currentEdition(): ?stringpublic function isLicenseValid(): boolpublic function isInGracePeriod(): boolpublic function hasCapability(CapabilityCode $capability): boolpublic function requireCapability(CapabilityCode $capability): voidpublic function getActivePacks(): arraypublic function trialPolicy(): TrialPolicypublic function __construct(private readonly ClientInterface $httpClient, private readonly RequestFactoryInterface $requestFactory, private readonly StreamFactoryInterface $streamFactory, private readonly StatusX5cValidator $statusValidator, private readonly string $baseUrl = self::DEFAULT_BASE_URL, #[SensitiveParameter] private readonly ?string $apiKey = null, array $pinnedPublicKeys = [], array $backupPins = [], private readonly OutboundHttpPolicy $policy = new OutboundHttpPolicy(allowedSchemes: ['https'], connectTimeoutSeconds: 10, readTimeoutSeconds: 30, maxResponseBytes: 10_485_760, followRedirects: false, allowPrivateTargets: false, retryAttempts: 0, retryBaseBackoffMs: 250))public function activate(string $licenseJws, string $deploymentId, ?string $machineFingerprintHash = null, ?string $expectedLicenseId = null, ?string $clientNonce = null): StatusResponsepublic function heartbeat(string $licenseJws, string $deploymentId, ?string $machineFingerprintHash = null, ?string $expectedLicenseId = null, ?string $clientNonce = null): StatusResponsepublic function deactivate(string $licenseJws, string $deploymentId, ?string $machineFingerprintHash = null, ?string $expectedLicenseId = null, ?string $clientNonce = null): StatusResponsepublic function verifyOnline(string $licenseJws, string $deploymentId, ?string $machineFingerprintHash = null, ?string $expectedLicenseId = null, ?string $clientNonce = null): StatusResponsepublic function getPinSet(): ?PinSetpublic function checkRenewal(string $licenseId, string $customerId): array行為合約
標題為「行為合約」的區段權益評估會產生五種當前狀態之一,每種都帶有一個固定的執行階段後果:
| 狀態 | 執行階段 | 生效能力 | 更新/支援 | 品牌標示 |
|---|---|---|---|---|
Active | 允許 | 所授權的版本 + active 套件包 | 是(付費);否(評估) | 無(付費);浮水印(評估) |
GracePeriod | 允許 | 所授權的版本 + active 套件包 | 是 | 無(付費) |
DowngradedToCore | 允許 | 僅 Core | 否 | 無(付費);浮水印(試用) |
EvaluationExpired | 受限 | 無 | 否 | 浮水印 |
NoLicense | 停用(fail-closed) | 無 | 否 | 浮水印 |
- 付費到期絕不會停用已安裝的執行階段。
runtimeAllowed在每個付費狀態下都維持 true。超過寬限窗後,生效版本會降級為 Core:Core 能力無限期繼續運作、所有 Pro/Enterprise/套件包能力都會閘掉,而更新與支援會被撤銷。已棄用的EntitlementStatus::PerpetualFallbackcase 指稱 6.0 之前的到期原則,僅為編譯相容而存在。 - 並非每個狀態都以 Core 執行收場。 Grace 會保留完整授權的版本,附一則指名日期的續約警告。評估到期會限制執行階段並保留浮水印。no-license 狀態會 fail-closed 地停用 Enterprise 路徑,並對任何舊版輸出明顯加上浮水印,因此它絕不會被誤認為付費執行。
- 固定的解析順序。 一份完全過期的授權不會解析出任何能力。套件包能力延後交由套件包自身的 active 狀態與獨立到期決定。版本能力則對照強制執行的生效版本、遵循嚴格的階層 Enterprise ⊇ Pro ⊇ Core 來解析。閘控絕不參照原始的
LicenseKey::$edition。 - 兩個計時器;最嚴格者勝出。 權益計時器(兩條通道皆適用)驅動 Active、Grace 與超過寬限後的 Core 降級。線上活躍計時器僅適用於線上模式為
required的ioncube通道:租約加上 48 小時的活躍寬限必須為有效。在租約之後離線時,premium 會暫停於 Core 層級;稍後一次已驗證的線上檢查會重新建立租約,premium 隨即恢復。signed_source通道——以及任何非 required 的線上模式——沒有活躍計時器,維持離線且具備 air-gap 能力。 - 反回捲是確定性的。 強制執行會對照一個單調下限來評估:生效時間取系統時鐘與上次已驗證時間的最大值,因此時鐘往回調不會給予額外時間。一次超過偏移容差與剩餘寬限的回捲,會強制觸發 Core 降級。
- 租約僅以受信任時間推進。 一次成功的線上檢查會記錄已驗證、已簽署的伺服器時間,絕不記錄本機時鐘,因此本機時鐘往前跳不會膨脹租約。
- 品牌標示只有一個權威。 只有評估通道會觸發浮水印。付費授權在每個狀態下都解析為無品牌標示,包含降級後的狀態。
- 試用是疊加在同一組計時器上的原則層。 吞吐量會被上限封頂(預設 2 pages/second)、評估浮水印會套用,而到期嚴格且不附寬限。只有回報的原因會改變;決定與生效版本仍依循付費機器。
- 線上合約是端到端簽署的。 每個請求都帶有一個 CSPRNG 用戶端 nonce。每個回應都是媒體型別為
application/nextpdf-status+jwt的 compact EdDSA JWS,經由 x5c 憑證鏈驗證回一個編入建置的釘選根憑證,並綁定到所送出的 nonce、已安裝的授權 id,以及所呼叫的端點。舊版未簽署封套傳輸已退役;恰好只剩一條信任路徑。所設定的 SPKI 釘選以X-NextPDF-Pinned-Public-Key標頭傳遞,而一個釘選集必須包含一個備援釘選。 - 沒有執行階段席次強制執行。
maxSlots僅為合約性質——一般運作沒有任何遙測,也沒有強制的回報主機(phone-home)。
邊界案例與失敗模式
標題為「邊界案例與失敗模式」的區段- 對一份完全過期的授權所做的能力檢查,會在任何套件包或版本邏輯執行之前,短路到「不可用」。
- 一個附加套件包可能在基礎授權為 active 時就已過期。屆時拒絕會讀作套件包已過期(
SPEC-LIC-002),而非未授權(SPEC-LIC-001),並列出目前 active 的套件包。 - 一個存在但無法讀取或無法解析的本機強制執行狀態檔,是一個竄改訊號。機器會以
EnforcementReason::CorruptStatefail-closed 到 Core 降級。一個正當缺席的檔案並非損毀,會正常繼續。 - 一次低於單調下限、且超過偏移容差(預設 120 seconds)與剩餘寬限的時鐘回捲,會確定性地強制觸發 Core 降級。
- 一份失效的活躍租約會使一份原本為 Active 的
ioncube授權降級;而權益到期則無論租約狀態如何都會降級。 evaluate(null)是 fail-closed:執行階段被禁止、浮水印會套用,而警告會指名定價 URL。需要未授權 Core 行為的主機必須直接建構 Core 引擎。- 一份過期的評估授權沒有寬限、也沒有回退;執行階段受限,而浮水印維持。
checkRenewal()把 HTTP 404 視為正常的「沒有可用的」回答。任何其他非 200 都會拋出LicenseClientException,而一個違反對外原則的download_url會在能逸出該方法之前先 fail-closed。- 一個偽造、竄改、過時、nonce 錯誤或授權錯誤的狀態回應絕不會產生
StatusResponse;驗證會拋出SpectrumAuthenticationException。一個已簽署但為revoked、expired或released的狀態會通過驗證,並以降級訊號的形式浮現(isLive()為 false)。 - 一個為不同交換而鑄造的狀態回應會被拒絕:已驗證的
response_type必須與所呼叫的端點相符。 FeatureGate在任何解析器錯誤時都 fail-closed:生效版本降為core,且不授予任何 premium 能力。EnforcedEntitlementResolver是端到端 fail-closed:其鏈中的任何例外都會產生 no-license(Core)結果與一筆 PSR-3 錯誤日誌,絕不授予 premium。
FIPS 模式行為
標題為「FIPS 模式行為」的區段授權層不加入任何自有的密碼學原則。授權與狀態簽章是由主機密碼學提供者執行的 Ed25519 驗證。在一個受 FIPS 限制的建置中,提供者未核可的演算法會在密碼學邊界上以明確錯誤失敗;不會有任何東西默默降級。這些是關於 NextPDF 程式碼的能力聲明:NextPDF 並非經驗證的密碼學模組、不持有任何認證,也不授予任何認證。
一致性
標題為「一致性」的區段| 聲明 | 標準 | 條款 |
|---|---|---|
| 授權檔案與狀態回應是 compact-serialization JWS,以 fail-closed 方式驗證。 | RFC 7515 | §3.1 |
| 授權與狀態簽章是 Ed25519。 | RFC 8032 | §5.1 |
| 已簽署回應中的 x5c 憑證鏈驗證回一個編入建置的釘選信任錨。 | RFC 5280 | §6.1 |
| 所設定的 SPKI 釘選集必須為一把尚未部署的金鑰包含一個備援釘選。 | RFC 7469 | §4.3 |
已驗證的 claims(iss、aud、exp、nbf、iat)是已註冊的 claim 名稱。 | RFC 7519 | §4.1 |
所有條款皆為改寫;未重現任何規範性文字。NextPDF 不作任何認證聲明。 支援不等於一致性,而一致性不等於認證。本模組以能力的形式讓其行為與所引用的條款對齊;一份授權或狀態封套是否被接受,是由 NextPDF 自己的釘選根憑證驗證器決定,而非由外部權威決定。
開發註記
標題為「開發註記」的區段- 以一個強制執行原則的傳輸來建構
LicenseClient(正規做法是SecurityAwareHttpClient)。原始的 PSR-18 用戶端會在建構時被拒絕,讓授權流量絕不會在缺少反 SSRF、不轉址、位元組上限與逾時控制的情況下送出。 - 呼叫端提供的用戶端 nonce 必須解碼為至少 16 bytes 的熵。原始碼將此界限依據於 RFC 8555 §6.5;該條款位於所引用語料之外,因此此界限僅依產品原始碼在此陳述。
- 透過
FeatureGate閘控功能;絕不要依原始的LicenseKey::$edition分支。該閘門只參照強制執行的生效版本,並 fail-closed 到core。 - 在新程式碼中不要對已棄用的
EntitlementStatus::PerpetualFallback做 switch。請改為測試isDowngradedToCore()或比對DowngradedToCore。 - 把一次能力拒絕視為一份補救酬載。請將它的結構化情境與升級 URL 呈現給呼叫端,而不要把它塌縮成一個泛用錯誤。
- 本模組涉及密碼學功能;在你自己的審查中請將它視為安全敏感。容量與席次條款由授權協議規範,而非由執行階段強制執行。本參考並非法律意見;請洽詢你自己的合規與法律顧問。
出版邊界
標題為「出版邊界」的區段本頁僅記載外部可觀察的行為與所支援的公開 API 介面。內部命名空間路徑、輔助類別、機制表、runbook 檔名,以及工單前綴皆不在範圍內。
另請參閱
標題為「另請參閱」的區段- Licensing — NextPDF Enterprise — 附設定與整合指引的能力頁面。
- Billing — 深入參考 — 方案層級、超量原則與結轉。
- SaaS — 深入參考 — 路由對應表所守護的多租戶服務介面。
- Branding — 深入參考 — 評估器所解析的品牌標示策略。