APIの更新項目とレスポンスの制限
入力スキーマ・更新権限・出力項目・取得量をそれぞれ確認します。
説明
APIがJSONを受け取るとき、DBの列へ入力項目を一括反映すると、画面にない権限情報まで変更できる場合があります。入力の項目と、更新してよい項目をアプリ側で決めます。
aliceの表示名を変更する小さなAPIを使い、roleを追加した要求を比較します。その後、出力の項目と一覧件数を制限し、拒否後の保存状態を照合します。
入力スキーマ・更新権限・出力項目・取得量をそれぞれ確認します。
1表示名と権限を持つ元表を用意する
本人に許すのは表示名nameの更新だけです。roleとemailはこの操作の対象外です。
from sd11 import *
lab = API()
print(lab.db.execute("SELECT * FROM users元の名前・権限・メールを照合する ORDER BY id").fetchall())aliceはviewer、bobはeditorです。以後は認証済み主体aliceのID1をサーバー側で指定する条件にします。
2一括反映が余分な項目も更新する例を試す
表示名のほかにroleを含むJSONを、入力項目を一括反映する処理へ渡します。
payload = json.dumps({"name":"Alice","role":"admin"})
lab.unsafe入力項目をそのまま列へ反映する(1,payload)
print(lab.db.execute("SELECT name,role FROM users WHERE id=1").fetchone())roleがadminへ変わりました。SQLをパラメーター化しても、更新してよい項目を制限しない問題は残ります。
3更新項目の許可リストへ変更する
元のviewerへ戻し、nameだけを受け付ける処理へ同じJSONを渡します。
lab.db.execute("UPDATE users SET name='alice',role='viewer' WHERE id=1")
lab.db.commit()
print(lab.update許可項目と型を検査してから更新する(1,payload))
print(lab.db.execute("SELECT name,role FROM users WHERE id=1").fetchone())400でalice/viewerを保ちます。余分な項目は黙って無視せず、この契約では要求全体を拒否します。
4通常の変更と型違いを比較する
正しいnameの後、配列をnameへ渡します。
print(lab.update(1,json.dumps({"name":"Alice"})許可された文字列の変更を送る))
print(lab.update(1,json.dumps({"name":["admin"]})値の型を変えた要求を送る))
print(lab.db.execute("SELECT id,name,role FROM users ORDER BY id").fetchall())正常な変更は200、型違いは400です。aliceの表示名だけが変わり、roleとbobを維持します。
5返却列も明示して必要な情報だけ出す
公開プロフィールの返却対象はidとnameです。
print(lab.public(1)公開用に選択した列を取得する)
print(lab.db.execute("SELECT email非公開列が元表に残ることを照合する FROM users WHERE id=1").fetchone())公開結果にemailは含まれませんが、元表には残っています。保存した情報をすべて直列化してから隠すより、取得・出力の段階で必要な列を選びます。
6一覧件数を制限し次の範囲を取得する
itemsは5件。最大2件でid順に取得し、最後のIDを次のafterへ渡します。
print(lab.list(2)上限内で最初の範囲を取得する)
print(lab.list(2,after=2前回の最後のIDより後を取得する))
print(lab.list(2,after=4))2件、2件、1件と返ります。返却件数だけでなく、DBのLIMITへ制約を渡しています。ID順のこの例では重複せず範囲を続けられます。
7無制限取得につながる値を拒否する
負数、上限超過、文字列を比較します。
for limit in件数の値と型を変えて拒否を確認する (-1,3,"2",True):
print(repr(limit),lab.list(limit))
print(lab.db.execute("SELECT COUNT(*) FROM items").fetchone())すべて400で返却行は空、元の5件は維持されています。複雑な集計や関連展開を提供するAPIでは件数に加え、展開深さや計算量の制限も必要です。
まとめ
SQLの値束縛と、更新できる項目の認可は別に設けます。
返却する列と件数を明示します。
拒否後も権限・他者の行・元データが保たれることを調べます。
操作履歴と解説を振り返れます。
Python 3.15 / 固定の公開試料
from sd11 import *
lab = API()
print(lab.db.execute("SELECT * FROM users ORDER BY id").fetchall())[(1, 'alice', 'viewer', 'alice@example.test'), (2, 'bob', 'editor', 'bob@example.test')]
payload = json.dumps({"name":"Alice","role":"admin"})
lab.unsafe(1,payload)
print(lab.db.execute("SELECT name,role FROM users WHERE id=1").fetchone())('Alice', 'admin')lab.db.execute("UPDATE users SET name='alice',role='viewer' WHERE id=1")
lab.db.commit()
print(lab.update(1,payload))
print(lab.db.execute("SELECT name,role FROM users WHERE id=1").fetchone())400
('alice', 'viewer')print(lab.update(1,json.dumps({"name":"Alice"})))
print(lab.update(1,json.dumps({"name":["admin"]})))
print(lab.db.execute("SELECT id,name,role FROM users ORDER BY id").fetchall())200 400 [(1, 'Alice', 'viewer'), (2, 'bob', 'editor')]
print(lab.public(1))
print(lab.db.execute("SELECT email FROM users WHERE id=1").fetchone())[(1, 'Alice')]
('alice@example.test',)print(lab.list(2))
print(lab.list(2,after=2))
print(lab.list(2,after=4))(200, [(1, 'pen'), (2, 'book')]) (200, [(3, 'paper'), (4, 'case')]) (200, [(5, 'bag')])
for limit in (-1,3,"2",True):
print(repr(limit),lab.list(limit))
print(lab.db.execute("SELECT COUNT(*) FROM items").fetchone())-1 (400, []) 3 (400, []) '2' (400, []) True (400, []) (5,)
演習完了