有効なトークンでも他人の文書は読めない
目標
認証・権限・所有権を分離し、同じ404でリソースの存在を隠します。
なぜ重要なのか
ログインしたユーザーが、アドレスの文書番号だけを変えて、ほかのチームの文書を読みました。トークンが有効だという事実と、特定の文書を読む権利は、別のものです。このラボでは、固定のトークン辞書を使って、認証サーバーの代わりにします。トークンを発行したり、JWTの署名を実装したりするラボではなく、認証の結果のあとの権限の境界を実装するラボです。
ステップ
/root/work/fa-ownership-lab/service.pyで、bearer(header)は、正確に'Bearer 'で始まり、そのあとに空白のないトークンが1つあるときに、トークンを返します。None・空のトークン・別のscheme・余分な空白は、ValueErrorです。
最初に一度だけ準備してください。既存のファイルは上書きしません。
mkdir -p /root/work/fa-ownership-lab
test -e /root/work/fa-ownership-lab/service.py || cp /opt/fixtures/ten_labs/fa-ownership-lab/service.py /root/work/fa-ownership-lab/service.py
cd /root/work/fa-ownership-lab
-
/root/work/fa-ownership-lab/service.pyで、principal(token, users)は、トークン辞書のユーザー{id, scopes}を返しますが、scopesのリストまでコピーします。知らないトークンは、ValueErrorです。 -
/root/work/fa-ownership-lab/service.pyで、require_scope(user, scope)は、scopesにscopeの文字列が正確にあるときはNone、なければPermissionErrorを出します。read-allは、readではありません。 -
/root/work/fa-ownership-lab/service.pyで、visible(user, document)は、documentがNoneではなく、ownerがuserのidと正確に同じときだけTrueです。 -
/root/work/fa-ownership-lab/service.pyで、public_document(document)は、idとtitleだけを持つ新しい辞書です。ownerやinternal_costは含みません。 -
/root/work/fa-ownership-lab/service.pyで、authenticate(header, users)は、bearerとprincipalをつなぎます。ValueErrorはHTTPException(401)で、headersのWWW-Authenticateの値はBearerです。 -
/root/work/fa-ownership-lab/service.pyで、read_document(user, documents, document_id)は、read scopeがなければHTTPException(403)、存在しないか他人の文書ならHTTPException(404)、それ以外ならpublic_documentの結果です。 -
/root/work/fa-ownership-lab/service.pyで、create_app(users, documents)は、GET /documents/{document_id}でAuthorizationヘッダーを受け取って、authenticateとread_documentを呼び出すFastAPIアプリを返します。200・401・403・404と、非公開フィールドの除去を、実際のリクエストで検証してください。
参考
- インターネットやパッケージのインストールなしで、既存のlab-dev環境で行います。
- 各ステップは、45秒の採点バジェットの中で実行されます。実際のsleepやネットワーク呼び出しを追加しないでください。
- 採点は、提出されたモジュールを新しく読み込んで、独立した入力と一時的なDBで検査します。期待値を定数で返す代わりに、契約を実装してください。
- FastAPI公式ドキュメント・pytest公式ドキュメント・Python sqlite3
- 限界: 固定のトークン辞書は、学習用の入力です。本番の認証には、有効期限・署名・取り消し・安全な保管が、追加で必要です。404を同じにするだけで、応答時間やアクセスログを通じたすべての推測が消えるわけでもありません。拒否されたリクエストが、元のデータまで変えていないかも、一緒に検査します。
Bearerヘッダーを分離する
/root/work/fa-ownership-lab/service.pyで、bearer(header)は、正確に'Bearer 'で始まり、そのあとに空白のないトークンが1つあるときに、トークンを返します。None・空のトークン・別のscheme・余分な空白は、ValueErrorです。
最初に一度だけ準備してください。既存のファイルは上書きしません。
mkdir -p /root/work/fa-ownership-lab
test -e /root/work/fa-ownership-lab/service.py || cp /opt/fixtures/ten_labs/fa-ownership-lab/service.py /root/work/fa-ownership-lab/service.py
cd /root/work/fa-ownership-lab
ヘッダーを任意に複数の断片に分けると、空白のエラーを正常なトークンとして受け入れてしまうことがあります。
保存したら、bash /opt/lab/checks/fa-ownership-lab/01-contract.shで確認してください。
身元をコピーして返す
/root/work/fa-ownership-lab/service.pyで、principal(token, users)は、トークン辞書のユーザー{id, scopes}を返しますが、scopesのリストまでコピーします。知らないトークンは、ValueErrorです。
返されたscopesを変更して、元のユーザーの権限まで変わってしまうと、リクエスト間で権限が混ざります。
保存したら、bash /opt/lab/checks/fa-ownership-lab/02-contract.shで確認してください。
権限は正確に比較する
/root/work/fa-ownership-lab/service.pyで、require_scope(user, scope)は、scopesにscopeの文字列が正確にあるときはNone、なければPermissionErrorを出します。read-allは、readではありません。
部分文字列の比較は、より長い権限名を、別の権限と取り違えます。
保存したら、bash /opt/lab/checks/fa-ownership-lab/03-contract.shで確認してください。
所有権を別に確認する
/root/work/fa-ownership-lab/service.pyで、visible(user, document)は、documentがNoneではなく、ownerがuserのidと正確に同じときだけTrueです。
リソースがないことと、他人の所有であることを、同じ判定にまとめます。
保存したら、bash /opt/lab/checks/fa-ownership-lab/04-contract.shで確認してください。
レスポンスのフィールドを許可リストで選ぶ
/root/work/fa-ownership-lab/service.pyで、public_document(document)は、idとtitleだけを持つ新しい辞書です。ownerやinternal_costは含みません。
元のデータからフィールドを削除せず、新しいレスポンスを組み立てます。
保存したら、bash /opt/lab/checks/fa-ownership-lab/05-contract.shで確認してください。
エラーをHTTPの契約に合わせる
/root/work/fa-ownership-lab/service.pyで、authenticate(header, users)は、bearerとprincipalをつなぎます。ValueErrorはHTTPException(401)で、headersのWWW-Authenticateの値はBearerです。
認証の失敗とアプリケーションのエラーを、500の1つにまとめません。
保存したら、bash /opt/lab/checks/fa-ownership-lab/06-contract.shで確認してください。
拒否の順序を固定する
/root/work/fa-ownership-lab/service.pyで、read_document(user, documents, document_id)は、read scopeがなければHTTPException(403)、存在しないか他人の文書ならHTTPException(404)、それ以外ならpublic_documentの結果です。
認証のあとでも、scopeと所有権は、それぞれ確認する必要があります。
保存したら、bash /opt/lab/checks/fa-ownership-lab/07-contract.shで確認してください。
実際のリクエストで境界を閉じる
/root/work/fa-ownership-lab/service.pyで、create_app(users, documents)は、GET /documents/{document_id}でAuthorizationヘッダーを受け取って、authenticateとread_documentを呼び出すFastAPIアプリを返します。200・401・403・404と、非公開フィールドの除去を、実際のリクエストで検証してください。
関数が個別に正しくても、経路で呼び出しを抜かすと、アクセス制御は適用されません。
保存したら、bash /opt/lab/checks/fa-ownership-lab/08-contract.shで確認してください。