diff --git a/tools/api-inventory/scripts/add_authmech.py b/tools/api-inventory/scripts/add_authmech.py index 3a26bd35d9..bbeb780b65 100644 --- a/tools/api-inventory/scripts/add_authmech.py +++ b/tools/api-inventory/scripts/add_authmech.py @@ -15,7 +15,7 @@ def _write(path, hd, data, newcols): """既存の同名列は **その位置のまま値を差し替える**。無い列だけ末尾に足す。 末尾に付け直すと列順が変わり、README の awk 例や他スクリプトの - 列位置前提(c[13]=impl_file 等)が壊れる。 + 列位置前提(_col(c,"impl_file")=impl_file 等)が壊れる。 """ pos = {n: i for i, n in enumerate(hd)} add_cols = [n for n in newcols if n not in pos] @@ -48,6 +48,14 @@ def _write(path, hd, data, newcols): def load(p): return [l.rstrip("\n").split("\t") for l in open(p,encoding="utf-8") if l.rstrip("\n")] rows=load(TSV); hd=rows[0]; data=rows[1:] +def _col(c, name, _cache={}): + """列名で引く。列の統合・追加で位置がずれても壊れないようにするため。""" + if not _cache: + _cache.update({n: i for i, n in enumerate(hd)}) + i = _cache.get(name) + return c[i] if i is not None and len(c) > i else "" + + filecache={} def get_file(fp): if fp in filecache: return filecache[fp] @@ -78,16 +86,16 @@ def func_src(fp,ln): # --- auth_mechanism: この行の認証はどこで定義されるか --- def col_mech(c): - at=c[2] # api_type - am=c[21] # auth_method + at=_col(c,"api_type") # api_type + am=_col(c,"auth_method") # auth_method if "ModelView" in at: return "modelview(Flask-Admin is_accessible/role_has_access)" if at=="フレームワーク": return "framework(invenio/flask-security既定)" # config駆動REST判定: uriにやREST系、blueprintが*_rest - bp=c[10] - if bp.endswith("_rest") or bp.endswith("_rest2") or "REST" in c[11] or "_options" in c[11]: + bp=_col(c,"blueprint") + if bp.endswith("_rest") or bp.endswith("_rest2") or "REST" in _col(c,"endpoint") or "_options" in _col(c,"endpoint"): return "config-factory(*_REST_ENDPOINTS permission_factory_imp)" if am=="admin-role-table": return "modelview/admin(role_has_access)" - if am in("none","rate-limit-only","不要") or c[20]=="不要": return "none(デコレータ無し・公開)" + if am in("none","rate-limit-only","不要") or _col(c,"auth_required")=="不要": return "none(デコレータ無し・公開)" if "action-need" in am: return "decorator(@x_permission.require)" if "record-permission" in am: return "decorator(@need_record_permission)" if "files-action" in am: return "decorator(@need_permissions)+ActionRole(グローバル付与注意)" @@ -99,14 +107,14 @@ def col_mech(c): # --- bola_risk: object-level認可(所有者/対象単位チェック)が実装にあるか --- OWNER=re.compile(r"created_by|check_created_id|owner|current_user\.(id|get_id)|weko_shared|can_edit|is_himself|has_permission|check_authority|activity_login_user|check_index_permission|permission_factory|need_record_permission|get_or_404|filter_by\([^)]*user") def col_bola(c,seg): - m=(c[4] or "GET").split(",")[0] + m=(_col(c,"method") or "GET").split(",")[0] # パスにリソースID(<...pid/id/recid...>)があるか - has_id=bool(re.search(r"<[^>]*(pid_value|recid|id|identifier|bucket_id|activity_id|group_id|key)", c[5])) + has_id=bool(re.search(r"<[^>]*(pid_value|recid|id|identifier|bucket_id|activity_id|group_id|key)", _col(c,"uri"))) if not has_id: return "N/A(リソースID無し)" - if "ModelView" in c[2]: return "admin-role-tableのみ(オブジェクト単位判定なし=管理者は全件)" + if "ModelView" in _col(c,"api_type"): return "admin-role-tableのみ(オブジェクト単位判定なし=管理者は全件)" if OWNER.search(seg): return "object-level認可あり(所有者/対象単位)" # sec_patternに所有者チェック欠落があれば実証済み - if len(c)>41 and "所有者チェック欠落" in c[41]: return "★object-level認可なし(BOLA・実証済)" + if "所有者チェック欠落" in _col(c,"sec_pattern"): return "★object-level認可なし(BOLA・実証済)" return "★object-level認可なし(要確認・ID直指定で他リソース操作の懸念)" mech_c=collections.Counter() if False else {} @@ -114,7 +122,7 @@ def col_bola(c,seg): import collections mc=collections.Counter(); bc=collections.Counter() for c in data: - seg=func_src(c[13],c[14]) if (len(c)>14 and str(c[14]).isdigit() and c[14]!="0") else "" + seg=func_src(_col(c,"impl_file"),_col(c,"impl_line")) if (str(_col(c,"impl_line")).isdigit() and _col(c,"impl_line")!="0") else "" mech=col_mech(c); bola=col_bola(c,seg) c += [mech,bola] mc[mech.split("(")[0]]+=1; bc[bola.split("(")[0]]+=1 diff --git a/tools/api-inventory/scripts/add_cols.py b/tools/api-inventory/scripts/add_cols.py index a1f3c8c1d0..7bcb124963 100644 --- a/tools/api-inventory/scripts/add_cols.py +++ b/tools/api-inventory/scripts/add_cols.py @@ -15,7 +15,7 @@ def _write(path, hd, data, newcols): """既存の同名列は **その位置のまま値を差し替える**。無い列だけ末尾に足す。 末尾に付け直すと列順が変わり、README の awk 例や他スクリプトの - 列位置前提(c[13]=impl_file 等)が壊れる。 + 列位置前提(_col(c,"impl_file")=impl_file 等)が壊れる。 """ pos = {n: i for i, n in enumerate(hd)} add_cols = [n for n in newcols if n not in pos] @@ -48,6 +48,14 @@ def _write(path, hd, data, newcols): def load(p): return [l.rstrip("\n").split("\t") for l in open(p,encoding="utf-8") if l.rstrip("\n")] rows=load(TSV); hd=rows[0]; data=rows[1:] +def _col(c, name, _cache={}): + """列名で引く。列の統合・追加で位置がずれても壊れないようにするため。""" + if not _cache: + _cache.update({n: i for i, n in enumerate(hd)}) + i = _cache.get(name) + return c[i] if i is not None and len(c) > i else "" + + # 実装関数のソース断片をキャッシュ srccache={} def get_src(fp,ln): @@ -70,12 +78,12 @@ def get_src(fp,ln): srccache[key]=seg; return seg def col_csrf(c,seg): - m=c[4]; + m=_col(c,"method"); if not re.search(r"POST|PUT|DELETE|PATCH",m): return "N/A(参照系)" if "csrf_random" in seg or "validate_csrf" in seg: return "手動csrf照合あり" # このアプリはCSRFProtect未初期化 → session認証の状態変更は無防備 - if c[21] in("session","session+guest") or "session" in c[21] or c[20]=="要": - if "oauth" in c[21] or "Bearer" in seg or "require_api_auth" in seg: return "OAuth(CSRF非該当)" + if _col(c,"auth_method") in("session","session+guest") or "session" in _col(c,"auth_method") or _col(c,"auth_required")=="要": + if "oauth" in _col(c,"auth_method") or "Bearer" in seg or "require_api_auth" in seg: return "OAuth(CSRF非該当)" return "★CSRF保護なし(CSRFProtect未初期化・状態変更)" return "CSRF該当外(未認証public)" @@ -109,7 +117,7 @@ def col_reslimit(seg): newcols=["csrf_protection","input_validation","audit_logged","triggers_task","resource_limit"] for c in data: - seg=get_src(c[13],c[14]) if c[14].isdigit() else "" + seg=get_src(_col(c,"impl_file"),_col(c,"impl_line")) if _col(c,"impl_line").isdigit() else "" c += [col_csrf(c,seg), col_input(seg), col_audit(seg), col_task(seg), col_reslimit(seg)] _write(TSV, hd, data, ['csrf_protection', 'input_validation', 'audit_logged', 'triggers_task', 'resource_limit']) # サマリ diff --git a/tools/api-inventory/scripts/add_dataop4.py b/tools/api-inventory/scripts/add_dataop4.py index 0b95589ab1..a80304876b 100644 --- a/tools/api-inventory/scripts/add_dataop4.py +++ b/tools/api-inventory/scripts/add_dataop4.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- -"""data_op_detail列: 取得/作成/更新/物理削除/論理削除 を実装から4区分評価""" +"""data_op列: 取得/作成/更新/物理削除/論理削除 を実装から4区分評価 + +(旧 data_op_detail。data_op と統合したため書き込み先を data_op に変更)""" import ast,re,os import os as _os, sys as _sys _sys.path.insert(0, _os.path.dirname(_os.path.abspath(__file__))) @@ -15,7 +17,7 @@ def _write(path, hd, data, newcols): """既存の同名列は **その位置のまま値を差し替える**。無い列だけ末尾に足す。 末尾に付け直すと列順が変わり、README の awk 例や他スクリプトの - 列位置前提(c[13]=impl_file 等)が壊れる。 + 列位置前提(_col(c,"impl_file")=impl_file 等)が壊れる。 """ pos = {n: i for i, n in enumerate(hd)} add_cols = [n for n in newcols if n not in pos] @@ -48,6 +50,14 @@ def _write(path, hd, data, newcols): def load(p): return [l.rstrip("\n").split("\t") for l in open(p,encoding="utf-8") if l.rstrip("\n")] rows=load(TSV); hd=rows[0]; data=rows[1:] +def _col(c, name, _cache={}): + """列名で引く。列の統合・追加で位置がずれても壊れないようにするため。""" + if not _cache: + _cache.update({n: i for i, n in enumerate(hd)}) + i = _cache.get(name) + return c[i] if i is not None and len(c) > i else "" + + # ファイル全体をキャッシュし、関数本体+同ファイル内で呼ぶヘルパも1段追う filecache={} def get_file(fp): @@ -102,11 +112,11 @@ def eval4(c,seg,method): nc=0 for c in data: - method=(c[4] or "GET").split(",")[0] - fp=c[13] if (len(c)>13) else ""; ln=c[14] if len(c)>14 else "0" + method=(_col(c,"method") or "GET").split(",")[0] + fp=_col(c,"impl_file") if (len(c)>13) else ""; ln=_col(c,"impl_line") if len(c)>14 else "0" # ModelView/frameworkは実パス無し→methodベース - if "ModelView" in c[2] or c[2]=="フレームワーク" or not str(ln).isdigit() or ln=="0": - act=c[11].split(".")[-1] if len(c)>11 else "" + if "ModelView" in _col(c,"api_type") or _col(c,"api_type")=="フレームワーク" or not str(ln).isdigit() or ln=="0": + act=_col(c,"endpoint").split(".")[-1] if len(c)>11 else "" if act=="delete_view": v="物理削除(Flask-Admin ModelView.delete_model=db.session.delete)" elif act=="create_view": v="作成" elif act=="edit_view": v="更新" @@ -118,5 +128,5 @@ def eval4(c,seg,method): v=eval4(c,seg,method) c.append(v) if "論理削除" in v or "物理削除" in v: nc+=1 -_write(TSV, hd, data, ['data_op_detail']) -print("data_op_detail付与。削除系(論理/物理):",nc,"列数:",len(hd)+1) +_write(TSV, hd, data, ['data_op']) +print("data_op付与。削除系(論理/物理):",nc,"列数:",len(hd)) diff --git a/tools/api-inventory/scripts/add_idempotency.py b/tools/api-inventory/scripts/add_idempotency.py index ddefde144c..f489ec31fc 100644 --- a/tools/api-inventory/scripts/add_idempotency.py +++ b/tools/api-inventory/scripts/add_idempotency.py @@ -13,7 +13,7 @@ def _write(path, hd, data, newcols): """既存の同名列は **その位置のまま値を差し替える**。無い列だけ末尾に足す。 末尾に付け直すと列順が変わり、README の awk 例や他スクリプトの - 列位置前提(c[13]=impl_file 等)が壊れる。 + 列位置前提(_col(c,"impl_file")=impl_file 等)が壊れる。 """ pos = {n: i for i, n in enumerate(hd)} add_cols = [n for n in newcols if n not in pos] @@ -45,6 +45,14 @@ def _write(path, hd, data, newcols): R = _os.environ.get("WEKO_ROOT", "/home/mhaya/wekov2") + "/" def load(p): return [l.rstrip("\n").split("\t") for l in open(p,encoding="utf-8") if l.rstrip("\n")] rows=load(TSV); hd=rows[0]; data=rows[1:] + +def _col(c, name, _cache={}): + """列名で引く。列の統合・追加で位置がずれても壊れないようにするため。""" + if not _cache: + _cache.update({n: i for i, n in enumerate(hd)}) + i = _cache.get(name) + return c[i] if i is not None and len(c) > i else "" + srccache={} def get_src(fp,ln): key=(fp,ln) @@ -61,7 +69,7 @@ def get_src(fp,ln): if s<=int(ln)<=e and (best is None or (e-s)<(best[1]-best[0])): best=(s,e) seg="\n".join(lines[best[0]-1:best[1]]) if best else ""; srccache[key]=seg; return seg def col_idem(c,seg): - m=c[4].split(",")[0] + m=_col(c,"method").split(",")[0] if m in("GET","HEAD"): return "N/A(参照系)" if m in("PUT","DELETE"): # 状態チェックあれば冪等 @@ -74,7 +82,7 @@ def col_idem(c,seg): return "-" nc=0 for c in data: - seg=get_src(c[13],c[14]) if (len(c)>14) else "" + seg=get_src(_col(c,"impl_file"),_col(c,"impl_line")) if (len(c)>14) else "" v=col_idem(c,seg); c.append(v) if "★" in v: nc+=1 _write(TSV, hd, data, ['idempotency']) diff --git a/tools/api-inventory/scripts/add_reqinfo.py b/tools/api-inventory/scripts/add_reqinfo.py new file mode 100644 index 0000000000..e63a699832 --- /dev/null +++ b/tools/api-inventory/scripts/add_reqinfo.py @@ -0,0 +1,209 @@ +# -*- coding: utf-8 -*- +"""機械で決まる列を実装から付与する(人手で埋める必要のない列を減らす)。 + + python3 add_reqinfo.py # 空欄/TODO のみ埋める + python3 add_reqinfo.py --fix-oauth-scope # oauth_scope の誤値も是正する + +対象: + query_params request.args / request.values の参照キー + body_params request.get_json / request.form / request.json の参照キー + request_content_type Content-Type の検査、または JSON を読むか + oauth_scope @require_oauth_scopes(...) の引数 + cache_ratelimit @limiter.limit(...) / cache デコレータ + api_version uri から導出(/v1, 等) + test_file impl_func 名でテストディレクトリを検索 + +いずれも **空欄/`-`/`TODO` のセルだけを埋める**。台帳の既存値は後から精査されており、 +一括再生成すると劣化するため(add_cols.py 等と同じ方針)。 + +例外: `--fix-oauth-scope` は oauth_scope に入っている **スコープではない値** +(`admin-role-table` 等)を `-` に戻す。OAuth スコープは `<資源>:<操作>` 形で、 +認証方式(auth_method / auth_mechanism)とは別物。手入力で 253 行に +`admin-role-table` が入っていた。 +""" +import argparse +import ast +import os +import re +import sys + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +from paths import data_path # noqa: E402 +from snapshot import default_weko_root # noqa: E402 + +EMPTY = ('', '-', 'TODO') +# スコープらしさは「<資源>:<操作> の並びを1つでも含むか」で見る。 +# 注記付きの正当な値(例: 'deposit:write(Authorizationヘッダ使用時)'、 +# 'invalid_scope(存在しないスコープ)')を落とさないため、完全一致にはしない。 +SCOPE_RE = re.compile(r'[a-z_]+:[a-z_]+|_scope\b') + +_src = {} + + +def seg_of(root, fp, ln, span=60): + """実装関数の周辺ソース。add_cols.py と同じ考え方。""" + if not fp or fp.startswith('(') or not str(ln).isdigit() or ln == '0': + return '' + key = (fp, ln) + if key in _src: + return _src[key] + path = os.path.join(root, fp) + try: + lines = open(path, encoding='utf-8', errors='replace').read().splitlines() + except OSError: + _src[key] = '' + return '' + i = int(ln) - 1 + _src[key] = '\n'.join(lines[max(0, i - 8):i + span]) + return _src[key] + + +def keys_from(seg, patterns): + out = [] + for pat in patterns: + out += re.findall(pat, seg) + seen, res = set(), [] + for k in out: + if k and k not in seen: + seen.add(k) + res.append(k) + return ';'.join(res[:8]) if res else '-' + + +def col_query(seg): + return keys_from(seg, [r"request\.args\.get\(\s*['\"]([\w\-]+)['\"]", + r"request\.args\.getlist\(\s*['\"]([\w\-]+)['\"]", + r"request\.values\.get\(\s*['\"]([\w\-]+)['\"]"]) + + +def col_body(seg): + k = keys_from(seg, [r"request\.form\.get\(\s*['\"]([\w\-]+)['\"]", + r"request\.form\[\s*['\"]([\w\-]+)['\"]", + r"(?:data|json|body)\.get\(\s*['\"]([\w\-]+)['\"]"]) + if k == '-' and re.search(r"request\.(get_json|json)\b", seg): + return 'JSON本文(キー不定)' + return k + + +def col_ctype(seg): + if re.search(r"request\.headers\[['\"]Content-Type", seg) or \ + re.search(r"content_type\s*[!=]=", seg): + return 'application/json(検査あり)' + if re.search(r"request\.(get_json|json)\b", seg): + return 'application/json' + if re.search(r"request\.form\b", seg): + return 'application/x-www-form-urlencoded' + if re.search(r"request\.files\b", seg): + return 'multipart/form-data' + return '-' + + +def col_scope(seg): + m = re.findall(r"require_oauth_scopes\(([^)]*)\)", seg) + scopes = [] + for arg in m: + scopes += re.findall(r"['\"]([\w:]+)['\"]", arg) + scopes += re.findall(r"(\w+_scope)\b", arg) + return ';'.join(dict.fromkeys(scopes)) if scopes else '-' + + +def col_cache(seg): + hits = re.findall(r"@limiter\.limit\(([^)]*)\)", seg) + if hits: + return 'rate-limit:' + re.sub(r"['\"]", '', hits[0])[:40] + if re.search(r"@cache|cached\(", seg): + return 'cache あり' + return '-' + + +def col_apiver(uri): + if '/v2/' in uri or uri.endswith('/v2'): + return 'v2' + if '/v1/' in uri or uri.endswith('/v1') or '' in uri: + return 'v1' + return '-' + + +def find_tests(root, impl_func, cache={}): + """impl_func 名を含むテストファイルを探す。""" + if not impl_func or impl_func in ('-', 'TODO'): + return '-' + if impl_func in cache: + return cache[impl_func] + hits = [] + for dp, dn, fn in os.walk(os.path.join(root, 'modules')): + if '/tests' not in dp: + continue + for f in fn: + if not f.startswith('test') or not f.endswith('.py'): + continue + p = os.path.join(dp, f) + try: + if impl_func in open(p, encoding='utf-8', errors='replace').read(): + hits.append(os.path.relpath(p, root)) + except OSError: + pass + if len(hits) >= 3: + break + cache[impl_func] = ';'.join(hits[:3]) if hits else '-' + return cache[impl_func] + + +def main(): + p = argparse.ArgumentParser(description='機械で決まる列を付与する') + p.add_argument('--full', default=None) + p.add_argument('--weko-root', default=None) + p.add_argument('--fix-oauth-scope', action='store_true', + help='oauth_scope に入っているスコープでない値を - に戻す') + p.add_argument('--with-test-file', action='store_true', + help='test_file も探す(全モジュール走査で時間がかかる)') + a = p.parse_args() + full = a.full or data_path('weko3_api_list_full.tsv') + root = a.weko_root or default_weko_root() + + lines = open(full, encoding='utf-8').read().rstrip('\n').split('\n') + hdr = lines[0].split('\t') + H = {n: i for i, n in enumerate(hdr)} + out = [lines[0]] + filled = {k: 0 for k in ('query_params', 'body_params', 'request_content_type', + 'oauth_scope', 'cache_ratelimit', 'api_version', 'test_file')} + fixed = 0 + for raw in lines[1:]: + c = raw.split('\t') + [''] * len(hdr) + c = c[:len(hdr)] + seg = seg_of(root, c[H['impl_file']], c[H['impl_line']]) + calc = { + 'query_params': col_query(seg), + 'body_params': col_body(seg), + 'request_content_type': col_ctype(seg), + 'oauth_scope': col_scope(seg), + 'cache_ratelimit': col_cache(seg), + 'api_version': col_apiver(c[H['uri']]), + } + if a.with_test_file: + calc['test_file'] = find_tests(root, c[H['impl_func']]) + # oauth_scope の誤値是正: スコープ形(<資源>:<操作>)でない値は落とす + if a.fix_oauth_scope: + cur = c[H['oauth_scope']] + if cur not in EMPTY and not SCOPE_RE.search(cur): + c[H['oauth_scope']] = '-' + fixed += 1 + for n, v in calc.items(): + if n not in H: + continue + if c[H[n]] in EMPTY and v != '-': + c[H[n]] = v + filled[n] += 1 + out.append('\t'.join(x.replace('\t', ' ') for x in c)) + + open(full, 'w', encoding='utf-8').write('\n'.join(out) + '\n') + print(f'{full}: {len(lines) - 1} 行') + for k, v in filled.items(): + if v or k in ('query_params', 'oauth_scope'): + print(f' {k:<22} 空欄を埋めた: {v}') + if a.fix_oauth_scope: + print(f' oauth_scope の誤値を - に戻した: {fixed}') + + +if __name__ == '__main__': + main() diff --git a/tools/api-inventory/scripts/add_row.py b/tools/api-inventory/scripts/add_row.py index 626ac2b034..85e5096499 100644 --- a/tools/api-inventory/scripts/add_row.py +++ b/tools/api-inventory/scripts/add_row.py @@ -13,7 +13,7 @@ auth_required / auth_method / auth_mechanism / api_version / last_commit系4列 `TODO` のまま残る: summary / response / status_codes / roles / data_op / - data_target / data_store / side_effects / config_deps / + data_store / side_effects / config_deps / test_file / notes / sec_* / dynamic_verified など、 **ソースを読まないと書けない列**。 diff --git a/tools/api-inventory/scripts/add_ssrf_redirect.py b/tools/api-inventory/scripts/add_ssrf_redirect.py index 4ebbb2af23..e524f273cb 100644 --- a/tools/api-inventory/scripts/add_ssrf_redirect.py +++ b/tools/api-inventory/scripts/add_ssrf_redirect.py @@ -15,7 +15,7 @@ def _write(path, hd, data, newcols): """既存の同名列は **その位置のまま値を差し替える**。無い列だけ末尾に足す。 末尾に付け直すと列順が変わり、README の awk 例や他スクリプトの - 列位置前提(c[13]=impl_file 等)が壊れる。 + 列位置前提(_col(c,"impl_file")=impl_file 等)が壊れる。 """ pos = {n: i for i, n in enumerate(hd)} add_cols = [n for n in newcols if n not in pos] @@ -48,6 +48,14 @@ def _write(path, hd, data, newcols): def load(p): return [l.rstrip("\n").split("\t") for l in open(p,encoding="utf-8") if l.rstrip("\n")] rows=load(TSV); hd=rows[0]; data=rows[1:] +def _col(c, name, _cache={}): + """列名で引く。列の統合・追加で位置がずれても壊れないようにするため。""" + if not _cache: + _cache.update({n: i for i, n in enumerate(hd)}) + i = _cache.get(name) + return c[i] if i is not None and len(c) > i else "" + + srccache={} def get_src(fp,ln): key=(fp,ln) @@ -88,7 +96,7 @@ def col_ssrf(seg): newcols=["redirect_target","ssrf_surface"] nr=ns=0 for c in data: - seg=get_src(c[13],c[14]) if (len(c)>14 and str(c[14]).isdigit() and c[14]!="0") else "" + seg=get_src(_col(c,"impl_file"),_col(c,"impl_line")) if (str(_col(c,"impl_line")).isdigit() and _col(c,"impl_line")!="0") else "" r=col_redirect(seg); s=col_ssrf(seg) if "★" in r: nr+=1 if "★" in s: ns+=1 diff --git a/tools/api-inventory/scripts/build_checklist.py b/tools/api-inventory/scripts/build_checklist.py index 42590a298c..e0a3513378 100644 --- a/tools/api-inventory/scripts/build_checklist.py +++ b/tools/api-inventory/scripts/build_checklist.py @@ -27,9 +27,9 @@ def j(parts,sep=" | "): return sep.join(p for p in parts if p) # auth: 要否+方式+仕組み auth = j([g(c,"auth_required"), g(c,"auth_method"), "["+g(c,"auth_mechanism").split("(")[0]+"]" if g(c,"auth_mechanism") else ""]) roles_scope = j([g(c,"roles"), ("scope:"+g(c,"oauth_scope")) if g(c,"oauth_scope") else ""]) - access_var = j([g(c,"auth_response_variance"), g(c,"restricted_content")], " / ") - data_op = g(c,"data_op_detail") or g(c,"data_op") - data_store = j([g(c,"data_target"), g(c,"data_store")], " → ") + access_var = g(c,"access_variance") + data_op = g(c,"data_op") + data_store = g(c,"data_store") side = j([g(c,"side_effects"), ("task:"+g(c,"triggers_task")) if g(c,"triggers_task") else ""]) # security_finding: sec_pattern中心にexposed/detail/evidenceを要約 sf_parts=[g(c,"sec_pattern")] diff --git a/tools/api-inventory/scripts/prioritize.py b/tools/api-inventory/scripts/prioritize.py index e4f8949dd9..c8fe78331e 100644 --- a/tools/api-inventory/scripts/prioritize.py +++ b/tools/api-inventory/scripts/prioritize.py @@ -119,14 +119,14 @@ def classify(c, H): uri = field(c, H, 'uri') auth = field(c, H, 'auth') or (field(c, H, 'auth_required') + ' | ' + field(c, H, 'auth_method')) - data_op = field(c, H, 'data_op') or field(c, H, 'data_op_detail') + data_op = field(c, H, 'data_op') finding = field(c, H, 'security_finding') or field(c, H, 'sec_pattern') dyn = field(c, H, 'dynamic_verified') cfg = field(c, H, 'config_deps') # full.tsv は data_target(操作対象) と data_store(保存先) が別列、 # 24列版は両者を結合した data_store 1列。どちらでも拾えるよう連結する。 - store = ' '.join(x for x in (field(c, H, 'data_target'), - field(c, H, 'data_store')) if x) + # data_target と data_store は access_variance と同様に1列へ統合済み + store = field(c, H, 'data_store') gap = field(c, H, 'test_gap') dep = field(c, H, 'deprecated')