ShareUrlPolicy
in package
プレビュー共有URLとして登録・使用してよいURLかを判定する。
DB・セッション・リクエスト変数には触れず、判定対象の URL を引数で受け取る。 DB アクセスやセッション操作は呼び出し側(Engine)が担う。
判定の基準は「そのURLが実際にどの URL コンテキストへ解決されるか」であり、解決には
a-blog cms 本体のルーティングパーサ parseAcmsPath() をそのまま使う。
Application も parseAcmsPath(rawurldecode(REQUEST_PATH)) の結果を $Q に載せているため、
ここでの判定と実際のリクエスト解決は一致する。
Why not セグメント名の文字列一致でパスを判定しない: 予約セグメントの多く(login / signin /
withdrawal 等)はパス末尾に来たときだけそのページに解決される規則なので、パス全体に対する
部分一致で判定すると login というコードのカテゴリー配下にある公開エントリー
(/login/how-to.html)まで拒否してしまう。逆に、値が複数セグメントに渡る予約語や
大文字小文字・エンコードの扱いを自前で再現すると、ルーティング側の規則が変わったときに
判定側が追随できず「実際には管理画面に解決されるのに共有できてしまう」方向のズレも生じ得る。
解析をパーサに委ねればこの二重管理そのものが無くなる。
拒否対象は「処理モードや対象を切り替えるコンテキスト」「非公開・会員セルフサービス領域」に限定する。 キーワード検索・タグ・並び替え・表示件数・期間指定・ユーザー別一覧・エイリアス別一覧のような 公開一覧の絞り込み条件は、匿名でも閲覧できる公開ページであり権限昇格や処理モードの変更に 関わらないため拒否しない。
クエリ文字列は一律拒否ではなく、危険なキー(reservedQueryKeys())だけを拒否する。 Why not クエリを一律拒否し続けない: utm_source やキーワード検索のようなクエリを含む公開ページの URL が共有できず実用に耐えない。危険なのは「特定のキーが $Get->get() 経由で処理モードや 権限の見え方を切り替える」ことであり、クエリの存在そのものではない。
Table of Contents
Methods
- isShareable() : bool
- 共有してよい URL か(絶対 URL を渡す。プレビュー用クエリは呼び出し側で除去済みであること)
- reservedQueryKeys() : array<int, string>
- 拒否する危険なクエリキー名(大文字小文字は区別しない)
- reservedUrlContextKeys() : array<int, string>
- 拒否する URL コンテキストのキー名(parseAcmsPath() が設定するキー)
- isShareablePath() : bool
- 共有してよいパスか
- isShareableQuery() : bool
- 危険なクエリキーを含まないか
- stripDirOffset() : string
- DIR_OFFSET に対応するパス接頭辞を取り除く
Methods
isShareable()
共有してよい URL か(絶対 URL を渡す。プレビュー用クエリは呼び出し側で除去済みであること)
public
static isShareable(string $url[, string|null $dirOffset = null ]) : bool
Parameters
- $url : string
- $dirOffset : string|null = null
-
CMS のインストール位置(DIR_OFFSET 相当)。null なら定数から取る。 Why not 常に DIR_OFFSET を直接読む: DIR_OFFSET は定義済み定数でプロセス内から 差し替えられないため、サブディレクトリ設置時の挙動を検証する手段が無くなる。 引数で受け取れるようにして、この公開メソッド 1 つだけで判定の全経路を テストできるようにしている(内部ヘルパを公開する代わりの境界シーム)。
Return values
boolreservedQueryKeys()
拒否する危険なクエリキー名(大文字小文字は区別しない)
public
static reservedQueryKeys() : array<int, string>
対象は「SUID/IS_PREVIEW/ACMS_SID が定義された状態(プレビュー共有によるなりすまし権限下)で
処理モードや権限の見え方を切り替えるキー」に限定する。ページング・並び替え・キーワード検索の
ようなクエリは公開ページの絞り込み条件でしかなく、なりすまし権限の有無で意味が変わらないため
含めない。洗い出しの一次情報は main.php / ACMS/function.php / ACMS/Filter.php の
$Get->get( $_GET[ 全数調査(PR 説明・チケットに記録)。
- layout: main.php の
$Get->get('layout') === 'edit'が LAYOUT_EDIT を有効化する。 ガードの sessionWithContribution(BID) は、共有 URL 経由の SUID が常にルート主管理者である ため常に真になり、レイアウト編集モードへの遷移を防げない。 - acms-preview-mode / timestamp / preview-token: Engine::shareUrlFormat() が既に除去するため 通常はここに到達しないが、除去処理が将来変わった場合に備えた多層防御として明示的にも拒否する。
Why not trash も拒否リストに入れる: trash=show は a-blog cms 自身がリビジョンプレビューの
URL に付けている(ACMS_GET_Admin_Entry_Revision_Info の 'url')。しかも main.php のエントリー
解決(ACMS_Filter::entrySession)は RVID が define される前に走るため、削除依頼
(entry_status = trash)状態のエントリーは trash クエリが無いとパスから解決できず 404 になる。
拒否すると承認フローの主要ユースケース「リビジョンプレビューを共有する」が丸ごと失敗する一方、
RVID が立っていれば同じ trash フィルタは下流でも解除されるため、拒否して得られる防御も薄い。
Return values
array<int, string>reservedUrlContextKeys()
拒否する URL コンテキストのキー名(parseAcmsPath() が設定するキー)
public
static reservedUrlContextKeys() : array<int, string>
「処理モードや対象を切り替えるコンテキスト」(管理画面・API・acms-code・プロキシ・ メディアダウンロード・URL 埋め込みセッションID)と「非公開・会員セルフサービス領域」 (ログイン系・パスワードリセット系・2FA系・プロフィール/メール/パスワード更新・退会)に限定する。
ここに挙げないキー(bid / aid / uid / cid / eid / utid / cmid / keyword / tag / field / order / page / limit / span / date)は、匿名でも閲覧できる公開ページの絞り込み条件でしかない。
Why not tpl も拒否する: tpl/path/to/template.html を表示してよいかは main.php の $isTplQueryForbidden(ajax_security_level / forbid_tpl_url_context / allow_tpl_path)が 既に持っている判断であり、ここで一律拒否すると同じ判断が二箇所に分かれる。しかも forbid_tpl_url_context のフォールバック既定は off(=匿名でも到達できる公開 URL)なので、 一律拒否は「誰でも開けるページのプレビューだけ共有できない」という不整合を生む。 なりすまし権限で tpl ガードの後方互換例外(sessionWithContribution)を素通りされる問題は、 ガードを持つ main.php 側で IS_PREVIEW を除外して塞ぐ。
Why not domain も拒否する: DOMAIN_SEGMENT は acmsPath() がリダイレクト先 URL を組む出力側の 構造で、parseAcmsPath() には対応する解析処理が無い(リクエストとして /domain/example.com/ を 受けても URL コンテキストにはならず、ただの未解決パスとして扱われる)。解決されないものを 拒否リストに載せても防御にならない。
Return values
array<int, string>isShareablePath()
共有してよいパスか
private
static isShareablePath(string $path) : bool
予約された URL コンテキストへ解決されないパスは、カテゴリーコードやエントリーコードのように 事前に列挙できないものであるため既定で通す(許可リストではなく拒否リスト方式)。
Parameters
- $path : string
-
デコード・正規化済みの URL パス部分(CMS ルートからの相対パス。先頭 / あり)
Return values
boolisShareableQuery()
危険なクエリキーを含まないか
private
static isShareableQuery(string $query) : bool
Parameters
- $query : string
-
URL のクエリ部分('' なら無し)。値ではなくキー名だけを見る。
Return values
boolstripDirOffset()
DIR_OFFSET に対応するパス接頭辞を取り除く
private
static stripDirOffset(string $path, string $dirOffset) : string
Parameters
- $path : string
- $dirOffset : string