要約

  • WebDAVでは、サーバーがロックごとに一意なトークンを生成する。Ifヘッダーにその値を入れることはトークンの提出を示すが、送信者の本人確認、書き込み権限、ロックの存続を保証しない。
  • 変更には別々の判定が要る。認証済みプリンシパル、操作別の権限、影響する全リソース、条件式とETag、直接・間接ロック、作成者または認可済みオーバーライド、コミット時点の状態である。
  • 共有ロック、無限深度のコレクション、COPY/MOVE、サーバーが選ぶタイムアウトにより、トークンは内容に付着して移動するリースにはならない。限定された調整証拠として扱うべきである。

ロック台帳が正しくても拒否は正しい

運用現場では「正しいトークンがあったか」が最初に調べられやすい。値が一致すれば、クライアントはロック所有者であり、書き込みを通すべきだったと考えてしまう。

しかし、RFC 4918の順序は違う。ロックされたリソースを変更するとき、サーバーは有効なロックトークンの提出だけでなく、認証済みプリンシパルがロック作成者と一致することを確認しなければならない。ロックを持つこと自体も、完全な変更権限を与えない。通常の認証と権限機構を別に適用する。

たとえば、ロック取得後に利用者の所属が変わり、書き込み権限だけが失われることがある。セッションが別アカウントに切り替わっても、クライアントプロセスは古いトークンを保持できる。管理者が障害復旧でロックを消しても、ネットワークから切れていたクライアントは知らない。MOVE先のコレクションだけが新規メンバーを拒む場合もある。

どれも、文字列が真正だったことと、今この操作を許可すべきことが別だと示す。トークンが答えるのは「どのロックを知っているか」であり、「誰か」「何をしてよいか」「どこまで変わるか」「現在も成立するか」ではない。

一意な値は強い権利ではない

WebDAVはHTTPに遠隔オーサリング、プロパティ、コレクション、名前空間操作、衝突回避を加えた。書き込みロックの中心目的は、複数の編集者が互いの変更を知らずに上書きするロストアップデートを減らすことにある。文書の所有権や業務上の正当性まで決める仕組みではない。

一つのロックには、サーバーが生成した一つの一意なトークンがある。クライアントはトークンを解釈してはならない。RFC 4918は、トークンURIが全リソース・全時間を通じて一意であることを要求する。新規LOCKに成功すると、サーバーはLock-Token応答ヘッダーと応答本文の両方で値を返す。

ここでの一意性は、別のロックを誤って参照しないための条件である。権限の委任ではない。DAV:lockdiscoveryによりアクティブなロックとトークンが読める構成も許されるため、トークンの秘匿だけに書き込み保護を任せることはできない。

RFC 4918はUUID URNを推奨し、恒久登録されたopaquelocktokenも残している。他の一意なURIも使える。RFC 9562は、推測困難性が必要なら暗号学的に安全な疑似乱数生成器を勧める。予測しにくさは望ましいが、UUIDの形式はACLを評価しない。

設計では次の六つを混同しない。

  1. 一意性はロック同士の取り違えを防ぐ。
  2. 推測困難性は偶然の発見を減らす。
  3. 提出はクライアントが値を知ることを示す。
  4. 認証は要求をプリンシパルに結び付ける。
  5. 認可はそのプリンシパルの操作範囲を決める。
  6. コミットは現在状態に対して変更を実在化させる。

トークンが担えるのは主に最初の三つであり、公開可能なロック情報を考えれば三つ目も秘密の証明ではない。

作成者、書き手、解除者は同じとは限らない

ロック作成者には、そのロックを用いて変更を調整する特別な関係がある。ただし通常の権限が残る範囲である。サーバーは、リソース所有者や管理者など別の特権プリンシパルにロック破棄を許してもよい。

RFC 3744は「書く」を分解する。DAV:write-contentは既存内容の変更、DAV:write-propertiesはdead propertyの変更、DAV:bindはコレクションへのメンバー追加、DAV:unlockはロック所有者以外によるUNLOCKを制御する。未割当URIへのPUTで新規リソースを作る場合は、親コレクションのbind権限が関わる。

したがって、書き込み権限を持つ利用者でも他者のロックを無視できない。一方、トークンを知る利用者もアクセス制御を回避できない。管理者の解除は、作成者を装う行為ではなく、別に認可されたオーバーライドとして記録する。

Heng Luが示す記録と権威の区別は、この構造にそのまま当てはまる。ロック台帳は調整状態を記述する。だが、本人を認証し、権限を評価し、現在の名前空間を解決し、ストレージを書き換える主体は別にある。記録が有用だからといって、記録そのものに主権を与える必要はない。

Ifには二つの意味が同居する

WebDAVのIfヘッダーは、通常の真偽条件としてだけ読むと意味を落とす。

第一に、状態トークンやETagに対する条件を表現する。同じstate list内の条件はANDで結ばれ、複数のリストはORの選択肢になる。Notは直後の条件を否定する。タグなしリストはRequest-URIに適用し、タグ付きリストは明示したリソースに適用する。

第二に、Ifにロックトークンが現れること自体が、そのトークンの提出になる。トークンを含むリストが最終的に真となる選択肢でなくても、提出した事実は失われない。複数リソースにまたがる操作で、条件評価と必要トークンの提出を同時に扱うためである。

この二重性は中継層に試練を与える。ゲートウェイが式を計算して真の分岐だけを残せば、別の影響リソースに必要なトークンを消してしまう。ログが「トークンあり」だけを残せば、ETag条件が偽だったことを見失う。提出された値と、評価されたリストと、その対象リソースを別々に保存する必要がある。

エラーも分けて読む。If条件が偽なら、認可確認後に412 Precondition Failedとなる。ロックされた影響リソースに必要なトークンが提出されていなければ、423 Lockedとlock-token-submitted事前条件が使われる。412は状態に関する主張の失敗、423は必要なロック証拠の不足を示し得る。どちらも単なる「ロックエラー」ではない。

Lock-Tokenヘッダーの用途はより限定的である。新規LOCKの成功応答でトークンを返し、UNLOCK要求で除去対象を指定する。PUT、PROPPATCH、COPY、MOVE、DELETEなどでは、状態トークンをIfで提出する。

画面に見える一つのURLでは足りない

ロックにはroot、scope、type、depthがある。ルートURLで作られた直接ロックに対し、コレクション上の無限深度ロックは子孫へ間接的に及ぶ。後からコレクションに加わったメンバーもその範囲に入る。

そのため、要求先が一つのファイルでも、上位コレクションのロックが適用される。フロントエンドがファイル自身のロックだけを照会しても、リポジトリの判断を再現できない。

LOCKのDepthは0またはinfinityで、指定がなければinfinityとなる。階層をロックする途中で互換性のないロックが見つかった場合、半分だけロックした状態を残してはならない。指定範囲について全体が成功するか失敗するかであり、Multi-Statusにより阻害したリソースを示せる。

COPY、MOVE、DELETEはさらに広い。削除は子だけでなく親コレクションのメンバー関係を変える。COPYやMOVEはsourceとdestinationを持ち、上書き先の既存状態も関係する。操作に必要な全てのロックトークンを提出しなければならない。

MOVEは「トークンが内容に付いて移動する」という誤解を明確に否定する。直接ロックは新しいURLへ持ち越されない。リソースは移動によって元コレクションの間接ロック範囲を離れ、移動先コレクションの無限深度ロック範囲へ入ることがある。意味を決めるのは現在の名前空間であって、内容に宿る恒久的権利ではない。

認可エンジンが一つのパスと一つのトークンしか受け取らないなら、二つのコレクション関係と上書き先を変える操作を正しく判断できない。影響リソース集合を先に展開し、コミット時に再確認する必要がある。

共有とは、複数のロックが共存すること

共有ロックは、互換性のある別の共有ロックを許す。それでもLOCKが一回成功するたびに、別々のロックと一意なトークンが作られる。共同作業者が一つの共通パスワードを持つのではない。

一つをrefreshしても、他の共有ロックの時間は延びない。一つをUNLOCKしても、リソース全体が自由になったとは限らない。別の共有ロックや上位からの間接ロックが残り得る。

編集の統合、レビュー担当、意見対立、最終版の決定はアプリケーションの仕事である。「shared」は技術的互換性を述べるだけで、多数決や共同所有を生まない。

管理画面は単純なlocked/unlocked表示ではなく、作成者、直接・間接、ルート、深度、共有・排他、各タイムアウト、どのUNLOCKが何を消したかを示すべきである。

タイムアウトの最終決定者はサーバー

クライアントはTimeoutで希望時間を提示できるが、サーバーは無視または変更できる。実際に与えられた値はサーバーが決める。

refreshは本文なしのLOCKで、Ifに一つのトークンを指定する。サーバーはDepthを無視し、成功時にそのロックのタイマーを再開し、他の共有ロックには触れない。応答本文のDAV:lockdiscoveryを更新し、新しいLock-Token応答ヘッダーは返さない。

refreshが失敗したら成功と仮定してはならない。また、ローカル計算上の期限前だからロックが必ず生きているとも言えない。管理オーバーライド、障害、状態喪失で早く消えることがある。逆に、ローカル時計が期限を越えた瞬間にサーバーが削除済みとも断言できない。

タイムアウトは更新義務と清掃の目安であり、硬いリースではない。重要な変更ではトークンを再提出し、サーバー状態とリソース版を確認して、現在時点の判断を受ける。

復元できる書き込み経路

信頼できる実装は次の段階を分離して残す。

  1. プリンシパルを認証する。
  2. 内容、プロパティ、bind、管理解除など具体的操作を認可する。
  3. Request-URI、destination、親、上書き対象を解決する。
  4. 直接・間接・排他・共有ロックを発見する。
  5. タグ付き/なしIf、ETag、Not、代替リストを完全に解析する。
  6. 必要な全トークンの提出と現在ロックへの対応を確認する。
  7. 認証済みプリンシパルを作成者または認可済みオーバーライドに結び付ける。
  8. コミット時に状態を再確認し、メソッドの定める範囲で全体を実行または拒否する。
  9. 認証、権限、条件、未提出、消失ロック、複数リソース競合を区別できる証拠を保存する。

生トークンを広範なログに残す必要はない。鍵付きフィンガープリントで相関し、プリンシパル、メソッド、影響集合、ロックroot/depth、条件結果、認可決定、実際のtimeout、最終版、応答コードを保つ。

試験では境界を交差させる。別プリンシパルが正しいトークンを提出する、権限はあるがトークンがない、トークンは正しいがETagが違う、新メンバーに無限深度ロックが及ぶ、階層LOCK失敗後に部分状態がない、MOVE後に直接ロックが移らず宛先ロックが働く、複数共有ロックの一つだけを更新・解除する、管理解除が帰属可能である、別APIや直接ストレージが無言で迂回できない――これらを確かめる。

WebDAVロックはロストアップデートの一部を防ぐ。業務上のデッドロック、意味的マージ、管理者侵害、ストレージ迂回、分散トランザクションまで解決しない。小さな共通仕様を小さく保つことが、実行可能で検証可能な調整を守る。

情報源