アクセス制御の欠陥を見つけるテスト行列
目標
認証なし・権限なし・他人の所有・正常なリクエストを分けた、否定テストを作成します。
なぜ重要なのか
成功レスポンスだけを確認するアクセス制御のテストは、すべて緑でした。ルートでHeaderの注入を忘れたり、scopeの検査を消したりしても、テストは壊れませんでした。検証者は、許可するケースを並べるだけでなく、拒否する理由ごとに別々に観測する必要があります。
ステップ
/root/work/test-auth-matrix-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: bearer(header)は、ちょうど'Bearer 'で始まり、その後に空白のないトークンが1つあるとき、トークンを返します。None・空のトークン・別のscheme・余分な空白はValueErrorです。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。
最初に1回だけ準備してください。既存のファイルは上書きしません。
mkdir -p /root/work/test-auth-matrix-lab
test -e /root/work/test-auth-matrix-lab/service.py || cp /opt/fixtures/ten_labs/test-auth-matrix-lab/service.py /root/work/test-auth-matrix-lab/service.py
test -e /root/work/test-auth-matrix-lab/test_service.py || cp /opt/fixtures/ten_labs/test-auth-matrix-lab/test_service.py /root/work/test-auth-matrix-lab/test_service.py
cd /root/work/test-auth-matrix-lab
-
/root/work/test-auth-matrix-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: principal(token, users)は、トークン辞書のユーザー{id, scopes}を返しますが、scopesのリストまでコピーします。未知のトークンはValueErrorです。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。 -
/root/work/test-auth-matrix-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: require_scope(user, scope)は、scopesにscopeの文字列がちょうど含まれているときNone、含まれていなければPermissionErrorを送出します。read-allはreadではありません。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。 -
/root/work/test-auth-matrix-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: visible(user, document)は、documentがNoneではなく、ownerがuserのidとちょうど等しいときだけTrueです。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。 -
/root/work/test-auth-matrix-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: public_document(document)は、idとtitleだけを持つ新しい辞書です。ownerやinternal_costは含みません。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。 -
/root/work/test-auth-matrix-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: authenticate(header, users)は、bearerとprincipalをつなぎます。ValueErrorはHTTPException(401)になり、headersのWWW-Authenticateの値はBearerです。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。 -
/root/work/test-auth-matrix-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: read_document(user, documents, document_id)は、read scopeがなければHTTPException(403)、存在しない、または他人のドキュメントならHTTPException(404)、それ以外ならpublic_documentの結果です。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。 -
/root/work/test-auth-matrix-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: create_app(users, documents)は、GET /documents/{document_id}でAuthorizationヘッダーを受け取り、authenticateとread_documentを呼び出すFastAPIアプリを返します。200・401・403・404と、非公開フィールドの除去を、実際のリクエストで検証してください。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。
参考
- インターネットやパッケージのインストールなしで、既存のlab-dev環境で行います。
- 各ステップは、45秒の採点バジェットの範囲内で実行されます。実際のsleepやネットワーク呼び出しを追加しないでください。
- 提出したテストは、別の一時フォルダーで、正常な実装と欠陥のある実装に対して実行されます。正常な実装では、実際に実行したテストがすべて成功し、欠陥のある実装では、テスト本文が失敗する必要があります。収集エラー、実行0件、すべてスキップ、強制終了は成功ではありません。pytestの基本機能と提供されたライブラリだけを使ってください。
- FastAPI公式ドキュメント・pytest公式ドキュメント・Python sqlite3
- 制限: 固定トークンの辞書は学習用の入力です。本番の認証には、有効期限・署名・取り消し・安全な保管が追加で必要です。404を同じにしたからといって、応答時間やアクセスログを通じたすべての推測がなくなるわけでもありません。拒否されたリクエストが元のデータまで変更していないかも、一緒に検査します。提供された実装は読んでもかまいませんが、採点は別のコピーを使用します。ソースの文言の検査やファイルの修正で欠陥を回避せず、公開インターフェースの実行結果を検査してください。
Bearerヘッダーを分離する(テスト)
/root/work/test-auth-matrix-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: bearer(header)は、ちょうど'Bearer 'で始まり、その後に空白のないトークンが1つあるとき、トークンを返します。None・空のトークン・別のscheme・余分な空白はValueErrorです。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。
最初に1回だけ準備してください。既存のファイルは上書きしません。
mkdir -p /root/work/test-auth-matrix-lab
test -e /root/work/test-auth-matrix-lab/service.py || cp /opt/fixtures/ten_labs/test-auth-matrix-lab/service.py /root/work/test-auth-matrix-lab/service.py
test -e /root/work/test-auth-matrix-lab/test_service.py || cp /opt/fixtures/ten_labs/test-auth-matrix-lab/test_service.py /root/work/test-auth-matrix-lab/test_service.py
cd /root/work/test-auth-matrix-lab
ヘッダーを適当に複数の断片に分けると、空白のエラーを正常なトークンとして受け入れてしまうことがあります。実装ファイルは修正しません。pytest.raisesで期待する例外を確認し、正常な結果には具体的な期待値をassertしてください。
保存後、bash /opt/lab/checks/test-auth-matrix-lab/01-contract.shで確認してください。
プリンシパルをコピーして返す(テスト)
/root/work/test-auth-matrix-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: principal(token, users)は、トークン辞書のユーザー{id, scopes}を返しますが、scopesのリストまでコピーします。未知のトークンはValueErrorです。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。
返されたscopesを変更したときに、元のユーザーの権限まで変わると、リクエスト間で権限が混ざります。実装ファイルは修正しません。pytest.raisesで期待する例外を確認し、正常な結果には具体的な期待値をassertしてください。
保存後、bash /opt/lab/checks/test-auth-matrix-lab/02-contract.shで確認してください。
権限を正確に比較する(テスト)
/root/work/test-auth-matrix-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: require_scope(user, scope)は、scopesにscopeの文字列がちょうど含まれているときNone、含まれていなければPermissionErrorを送出します。read-allはreadではありません。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。
部分文字列での比較は、より長い権限名を別の権限と取り違えます。実装ファイルは修正しません。pytest.raisesで期待する例外を確認し、正常な結果には具体的な期待値をassertしてください。
保存後、bash /opt/lab/checks/test-auth-matrix-lab/03-contract.shで確認してください。
所有権を別に確認する(テスト)
/root/work/test-auth-matrix-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: visible(user, document)は、documentがNoneではなく、ownerがuserのidとちょうど等しいときだけTrueです。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。
リソースがない場合と他人の所有を、同じ判定にまとめます。実装ファイルは修正しません。pytest.raisesで期待する例外を確認し、正常な結果には具体的な期待値をassertしてください。
保存後、bash /opt/lab/checks/test-auth-matrix-lab/04-contract.shで確認してください。
レスポンスのフィールドを許可リストで選ぶ(テスト)
/root/work/test-auth-matrix-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: public_document(document)は、idとtitleだけを持つ新しい辞書です。ownerやinternal_costは含みません。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。
元のデータからフィールドを消さず、新しいレスポンスを組み立てます。実装ファイルは修正しません。pytest.raisesで期待する例外を確認し、正常な結果には具体的な期待値をassertしてください。
保存後、bash /opt/lab/checks/test-auth-matrix-lab/05-contract.shで確認してください。
エラーをHTTPの契約に合わせる(テスト)
/root/work/test-auth-matrix-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: authenticate(header, users)は、bearerとprincipalをつなぎます。ValueErrorはHTTPException(401)になり、headersのWWW-Authenticateの値はBearerです。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。
認証の失敗とアプリケーションのエラーを、500の1つにまとめません。実装ファイルは修正しません。pytest.raisesで期待する例外を確認し、正常な結果には具体的な期待値をassertしてください。
保存後、bash /opt/lab/checks/test-auth-matrix-lab/06-contract.shで確認してください。
拒否の順序を固定する(テスト)
/root/work/test-auth-matrix-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: read_document(user, documents, document_id)は、read scopeがなければHTTPException(403)、存在しない、または他人のドキュメントならHTTPException(404)、それ以外ならpublic_documentの結果です。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。
認証の後でも、scopeと所有権はそれぞれ確認する必要があります。実装ファイルは修正しません。pytest.raisesで期待する例外を確認し、正常な結果には具体的な期待値をassertしてください。
保存後、bash /opt/lab/checks/test-auth-matrix-lab/07-contract.shで確認してください。
実際のリクエストで境界を閉じる(テスト)
/root/work/test-auth-matrix-lab/test_service.pyで、提供されたservice.pyの次の公開契約をテストしてください: create_app(users, documents)は、GET /documents/{document_id}でAuthorizationヘッダーを受け取り、authenticateとread_documentを呼び出すFastAPIアプリを返します。200・401・403・404と、非公開フィールドの除去を、実際のリクエストで検証してください。正常な実装では成功し、この契約に違反する実装では、実際のテスト本文の失敗として検出する必要があります。前のステップのテストを維持したまま、test_関数を追加してください。
関数が個別に正しくても、ルートで呼び出しを忘れると、アクセス制御は適用されません。実装ファイルは修正しません。pytest.raisesで期待する例外を確認し、正常な結果には具体的な期待値をassertしてください。
保存後、bash /opt/lab/checks/test-auth-matrix-lab/08-contract.shで確認してください。