LicenseResponseValidator
in package
アクティベーションサーバーからのライセンス応答 JSON を検証する。
Application::loadLicense() は license.php を require した「後」に LICENSE_HASH の照合を行うため、 require より前(= license.php を書き出す時点)の値検証が唯一の防御層になる。 このクラスはグローバル状態(AcmsLogger 等)に触れない純粋な検証本体として切り出し、 ログ記録は呼び出し側(Application)に閉じる。
PHP ソース注入は LicenseFileContentBuilder の var_export() 化で完全に閉じているため、ここでの検証は 「注入の第2防衛線」と「明らかに壊れた応答の早期検出」を目的とし、業務ルールの強制ではない。 業務ルールを強制すると、サーバー側の値が想定と1つ違うだけで全ユーザーのアクティベーションが止まる。 コードベースが実際に消費している値の集合を追跡できない項目については拒否ではなく既存の グレースフルデグレード(例: edition の switch の default 分岐)に揃える。
応答の実形式はアクティベーションサーバー(mypage の Api\LicenseActivation::activation)の実装で確認済み。 expire は 'Y-m-d H:i:s'、blog_limit / option.* は plans の int カラム、payment は cancel / delete が 応答前に例外になるため active / unpaid の2値、plan は plans.name の表示名(日本語)。 固定用の特徴付けテストは LicenseResponseValidatorTest::actualServerResponseProvider にある。
Table of Contents
Constants
- ALLOWED_EDITIONS = ['standard', 'professional', 'enterprise']
- ALLOWED_PAYMENTS = ['active', 'unpaid', '']
- DOMAIN_PATTERN = '/\A[A-Za-z0-9._\-:]{1,255}\z/'
- domain は LICENSE_DOMAIN として「登録済みライセンスドメイン(親)」に使われ、実アクセス host が そのサブドメインでよい設計(ablogcms/php/main.php:1321 の substr によるサフィックス一致判定、 ablogcms/php/Services/Blog/Helper.php の strpos 判定)のため、要求時に送った DOMAIN との完全一致は 強制しない。ここでは PHP ソース注入に使われる文字(引用符・バックスラッシュ等)を 除いた文字種のみ検証する。
- EDITION_PATTERN = '/\A[a-z0-9_-]{1,32}\z/'
- edition の文字種検証。ホワイトリスト判定の前段として、危険な文字列でないことだけを確認する。
- MAX_BLOG_LIMIT = 2147483647
- UNLIMITED_NUMBER_OF_USERS(Application::__construct で定義される定数)と同じ意味の値だが、 このクラスをグローバル定数から独立させるためにここで複製している。
- PLAN_PATTERN = '/\A[^<>"\'\\\\{}\x00\r\n]{1,255}\z/u'
- plan には分岐条件として消費されている値の集合が存在しない(class docblock 参照)ため、 人間可読なプラン名(日本語・空白・ピリオド等)を広く許可しつつ、PHP ソース注入および %{SUBSCRIPTION_PLAN} 経由の HTML 注入に使われる文字だけを除外する。 ACMS/function.php の setGlobalVars() は %{...} 変数を json_decode('"' . $val . '"') に通して バックスラッシュエスケープを展開してから str_replace で生置換するため、バックスラッシュは 特に必須で除外する。
Methods
- validate() : array{domain: string, hash: string, blogLimit: int, expire: string, plan: string, payment: string, edition: string, optionSubdomain: int|null, optionPlusdomain: int}
- toNonNegativeInt() : int|null
- 旧実装は引用符なしの数値連結だったため、サーバーが数値文字列(例: "10")を返しても 正しい PHP として動作していた。int 型のみを要求すると、これまで動いていた応答形式を 拒否してしまう可能性があるため、数値文字列も受理して int にキャストする。 licenseHasUnlimitedUsers()(ACMS/function.php)が LICENSE_BLOG_LIMIT を strict 比較するため、 型を int に統一することは必須。
- validateBlogLimit() : int
- validateDomain() : string
- validateEdition() : string
- validateExpire() : string
- validateHash() : string
- validateOption() : array{subdomain: int|null, plusdomain: int}
- validatePayment() : string
- validatePlan() : string
Constants
ALLOWED_EDITIONS
private
array<int, string>
ALLOWED_EDITIONS
= ['standard', 'professional', 'enterprise']
導出根拠: ablogcms/php/ACMS/GET/Admin/Dashboard/LicenseStatus.php の switch (LICENSE_EDITION) が 'enterprise' / 'professional' / 'standard' の3値を分岐条件にし、default で standard 相当に グレースフルデグレードしている。ablogcms/php/ACMS/function.php の isProfessional() (1158行目付近) / isEnterprise() (1168行目付近) も 'professional' / 'enterprise' のみを比較する。 ここでの判定も同じ挙動に揃え、ホワイトリスト外の値は拒否せず standard にフォールバックする (将来サーバー側に新エディションが追加された場合に既存 CMS を一斉停止させないため)。
ALLOWED_PAYMENTS
private
array<int, string>
ALLOWED_PAYMENTS
= ['active', 'unpaid', '']
導出根拠: ablogcms/themes/system/admin/checklist.html および
ablogcms/themes/system/admin/_layouts/top.html の %{SUBSCRIPTION_PAYMENT} が
/eq/active と /eq/unpaid の2値をテンプレート分岐条件にしている。
ablogcms/php/ACMS/function.php:6100 の
'%{SUBSCRIPTION_PAYMENT}' => ... LICENSE_PAYMENT ? LICENSE_PAYMENT : '' が falsy(空文字含む)を
許容しているため、空文字も許可値に加える。
DOMAIN_PATTERN
domain は LICENSE_DOMAIN として「登録済みライセンスドメイン(親)」に使われ、実アクセス host が そのサブドメインでよい設計(ablogcms/php/main.php:1321 の substr によるサフィックス一致判定、 ablogcms/php/Services/Blog/Helper.php の strpos 判定)のため、要求時に送った DOMAIN との完全一致は 強制しない。ここでは PHP ソース注入に使われる文字(引用符・バックスラッシュ等)を 除いた文字種のみ検証する。
private
mixed
DOMAIN_PATTERN
= '/\A[A-Za-z0-9._\-:]{1,255}\z/'
EDITION_PATTERN
edition の文字種検証。ホワイトリスト判定の前段として、危険な文字列でないことだけを確認する。
private
mixed
EDITION_PATTERN
= '/\A[a-z0-9_-]{1,32}\z/'
MAX_BLOG_LIMIT
UNLIMITED_NUMBER_OF_USERS(Application::__construct で定義される定数)と同じ意味の値だが、 このクラスをグローバル定数から独立させるためにここで複製している。
private
mixed
MAX_BLOG_LIMIT
= 2147483647
PLAN_PATTERN
plan には分岐条件として消費されている値の集合が存在しない(class docblock 参照)ため、 人間可読なプラン名(日本語・空白・ピリオド等)を広く許可しつつ、PHP ソース注入および %{SUBSCRIPTION_PLAN} 経由の HTML 注入に使われる文字だけを除外する。 ACMS/function.php の setGlobalVars() は %{...} 変数を json_decode('"' . $val . '"') に通して バックスラッシュエスケープを展開してから str_replace で生置換するため、バックスラッシュは 特に必須で除外する。
private
mixed
PLAN_PATTERN
= '/\A[^<>"\'\\\\{}\x00\r\n]{1,255}\z/u'
Methods
validate()
public
static validate(mixed $json) : array{domain: string, hash: string, blogLimit: int, expire: string, plan: string, payment: string, edition: string, optionSubdomain: int|null, optionPlusdomain: int}
Parameters
- $json : mixed
-
json_decode() の戻り値。int/array/string/null 等、object 以外も想定して受け取る
Return values
array{domain: string, hash: string, blogLimit: int, expire: string, plan: string, payment: string, edition: string, optionSubdomain: int|null, optionPlusdomain: int}toNonNegativeInt()
旧実装は引用符なしの数値連結だったため、サーバーが数値文字列(例: "10")を返しても 正しい PHP として動作していた。int 型のみを要求すると、これまで動いていた応答形式を 拒否してしまう可能性があるため、数値文字列も受理して int にキャストする。 licenseHasUnlimitedUsers()(ACMS/function.php)が LICENSE_BLOG_LIMIT を strict 比較するため、 型を int に統一することは必須。
private
static toNonNegativeInt(mixed $value) : int|null
Parameters
- $value : mixed
Return values
int|nullvalidateBlogLimit()
private
static validateBlogLimit(stdClass $json) : int
Parameters
- $json : stdClass
Return values
intvalidateDomain()
private
static validateDomain(stdClass $json) : string
Parameters
- $json : stdClass
Return values
stringvalidateEdition()
private
static validateEdition(stdClass $json) : string
Parameters
- $json : stdClass
Return values
stringvalidateExpire()
private
static validateExpire(stdClass $json) : string
Parameters
- $json : stdClass
Return values
stringvalidateHash()
private
static validateHash(stdClass $json) : string
Parameters
- $json : stdClass
Return values
stringvalidateOption()
private
static validateOption(stdClass $json) : array{subdomain: int|null, plusdomain: int}
Parameters
- $json : stdClass
Return values
array{subdomain: int|null, plusdomain: int}validatePayment()
private
static validatePayment(stdClass $json) : string
Parameters
- $json : stdClass
Return values
stringvalidatePlan()
private
static validatePlan(stdClass $json) : string
Parameters
- $json : stdClass