🍊

Pleasanter のロックの仕様について調べてみたら使わないほうがよさそうだった

2022/12/19に公開1

2022 個人アドベントカレンダー の記事です。

これはなに

  • 以前の記事で、ロックの意図しない動きとして、読取専用時のロック解除が可能である件を記載したが、アクションとして「更新」を発動していることに気付いたので深掘ってみた

問題状況:以前に確認したことと困ったこと

  • 前提問題として
    • 状況による制御で "読取専用" をかけていても、ロックを外すことができる。 > 以前の記事のこのセクション
    • 一方、読取専用なのでロックを戻すことはできなくなる。[1]
  • 直近困ったこと
    • ^ について、個人的には読取専用であるのに、ロックを外せてしまうのは問題だと思ったが仕様ならそれはそれで已むなしと考えた
    • しかし、「更新前」のサーバスクリプトで一定の条件で model.Lockedtrue にする処理を作っていたところ、ロックを外す行為で、「更新前」条件が発動し、再ロックがかかる事象になってしまった。
      • 読取専用と独立して発動する"ロック解除"は、"更新"にならないで欲しい。というか、ユーザが更新できてしまう、読取専用とは 🤔

調査 1:アクセス権限的に更新権限がない場合もロックが解除できる

以下のようにしてもロックは解除できる。

  • 全てのユーザにサイトの書き込み権限を付与
  • User1 でレコードをロック
  • User1 をサイトのアクセス権で、更新権限なしにする(読取のみチェック) → この後にロック解除可能

亜種として、レコードのアクセス権限が変更されて更新権限失った場合もロック解除可能。具体的には、

  • サイトのアクセス制御でレコードの読取権限を設定
  • テーブルの管理で、ユーザを選択する分類項目を利用して、更新時に分類項目で指定したユーザに更新権限を付与
  • 当該分類項目に User1 を指定して保存
  • User1 が分類項目を自分以外にするとともにロック → この後にロック解除可能

調査 2:ロック解除時の内部実行状況

  • ロックを解除をした際の動作について、サーバスクリプトで context を観測
try {
  model.DescriptionB = `${context.Action}  ${context.ControlId}`;
  context.Log(context.Action);
  context.Log(context.ControlId);
} catch (e) {
  context.Log(e.message);
}
  • context.Actionunlockrecord
    • 更新は update
  • context.ContrlId''(空文字)
    • 更新ボタンは UpdateCommand(他にプロセスだと Process_1 など)

重ねての確認として、スクリプトの $p.apiUpdate および API を直接実行したが、このどちらも context.Actionupdatecontext.ContrlId''(空文字) だった。

context.Actionunlockrecord であることで、ロック解除を固有に識別できそうか。

調査 3:ロック時の API による更新

  • ロックをしていないユーザの API キーを利用しても、ロックをしたユーザの API キーを利用しても、 API で更新することはできない。
{
  "Id": 123456,
  "StatusCode": 405,
  "Message": "レコード 123456 は User1 が 2022/12/18 00:00:00 にロックしました。"
}

もちろん API でロックをかける({Locked:true} で update API を実行する)ことは可能。

⇒ API でロックを外す方法がなさそう。

調査 4:ロックしたユーザの削除

調査をしている過程で、特権ユーザがいない状況(SaaS 環境)で、ロックをかけたユーザ自身が編集権を失うと、ロックが外せなくなってしまうことを危惧してこの仕様となったのではないだろうか、と考えた。

前提として、レコードのコピーや削除は、他のユーザも可能なので、「ロックは何らかの方法で外せることが必要」と言っていいかは仕様として検討を要する問題だと思う。
が、それはそれとして、ロックをしたユーザの削除は禁止されない(ロックしたユーザが消えれば結局はロックは外せない)のではないかと考えた。

→ 結論としてユーザは削除可能だった。つまり、ロックが解除できない事態を強く回避しようとしたとは考えにくい動作。

同時に、削除しているにもかかわらず、「レコード 123456 は User1 が 2022/12/18 00:00:00 にロックしました。」と表示された。
このとき User1 は、ログイン ID ではなく名前(表示上の名前)であった。

→ 例えば、サイトを作成したユーザを消すと、テーブルの管理で「作成日時」の横に出る作成者は"(未設定)"になるはずが、そうなっていない。
⇒ ID 持ちされていないことを意味している可能性がある。ので、次の調査に進む。

調査 5:ロックしたのと同じユーザ名でのロック解除

ロックしたユーザを消した後に同名のユーザを作成してレコードを見たところ

  • ロックしているのに「レコード 123456 は User1 が 2022/12/18 00:00:00 にロックしました。」のメッセージがない
    • ロックのチェックボックス事態は disable 状態でチェックが付いた状態で見える
  • 更新ボタンが出現している

ここで、更新ボタンを押すと

  • 「レコード 123456 は User1 が 2022/12/18 00:00:00 にロックしました。」のメッセージが出る
  • ロックが disable 状態でなくなる
  • → ロック解除可能

となった。
ただ、ロックしたユーザが削除されていない場合で、名前だけ合致させても再現しなかった。

まさか、ユーザが消された場合にだけ抜け穴を用意したとでも言うのだろうか。

調査 6:読取専用とロックが両方かかっているとき、ロック解除をトリガーとするサーバスクリプトの発動でデータが変更できる

前述のとおり「更新前」の条件を指定したサーバスクリプトは"ロック解除"をトリガーに発動するが、このときサーバスクリプトで model に値を設定すると、データ変更が適用される。
従って、状況が完了であることを条件に「状況の制御」で"読取専用"としていれば、更新ができない結果、データ改変が起きないものが、安全を期して状況を完了にするプロセスやスクリプトでロックをかけてしまうと、ロックを外すことでデータが書き換わってします危険を生じかねない。

まとめ

  • ロックは使わないほうがいい
    • 読取専用を活用したほうがよさそう
  • サーバスクリプトは使わないほうがいい
    • 予期しないタイミングで発動すると、想定しない状況でのデータ変更を招くので、どういうアクションで何をすべきか、を厳密に洗い出さないといけない。そしてそれは結構難しい

→ ノーコードでの対応を模索したほうがいい。

脚注
  1. 例えば、ロックが"オン"であることを条件に状況の制御での読取専用にしていたりすると、ロックオフの場合に読取専用は解除されて、再びロック可能になるかもしれない。だが、ロックを外すことが読取専用で防げない以上、このような状況の制御をする意義に乏しい。適当な設定ではないが想定できるとすれば、ロックした人のみが更新を許すか否かの裁量を握りたい場合が考えられる。ただ、そのようなことがしたい場合には、レコードのアクセス制御や担当者管理者の自分チェックを使った状況による制御がより相応しいと考えられる。 ↩︎

GitHubで編集を提案

Discussion