Skip to content

fix: 公開向けの配信・統計・補助 API で公開状態と閲覧権限を確認する - #1926

Merged
ivis-miyachi merged 8 commits into
develop_v2.1.0from
fix/public-visibility-check
Sep 29, 2026
Merged

ivis-miyachi merged 8 commits into
develop_v2.1.0from
fix/public-visibility-check

Conversation

@mhaya

@mhaya mhaya commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

概要 (Summary)

  • invenio-resourcesyncserver: レコード単位の経路にデコレータ public_record_required を付ける。changelist / changedump の検索条件を公開または削除済みのアイテムに限る(削除の通知は引き続き出す)。manifest に列挙するファイルをダウンロードできるものに限る
  • weko-sitemap: サイトマップに載せるアイテムを、公開状態でインデックスの閲覧権限を満たすものに限る
  • invenio-stats: 閲覧数・ファイル統計の取得に、レコードの閲覧権限(page_permission_factory)を要求するデコレータを付ける
  • weko-signposting: recid_signposting に page_permission_factory を設定する(同じ経路の recid と揃える)
  • weko-search-ui: インデックス名の取得で、閲覧できないインデックスを結果から除く
  • weko-items-ui: BibTeX 出力の検証で、閲覧できないレコードを存在しないレコードと同じ扱いにする
  • 背景: 公開向けの配信・統計・補助 API で、アイテムの公開状態やインデックスの閲覧権限が確認されていなかった
  • 挙動の変化: ResourceSync・サイトマップに非公開・公開日未到来のアイテムが出なくなる / 統計は閲覧できないレコードなら 403、存在しないレコードなら 404(従来は 0 件で 200)、不正な入力は 400(従来は 500)/ signposting は閲覧できないアイテムなら詳細画面と同じ応答 / 検索画面のパンくずで閲覧できないインデックスの名前が空になる / BibTeX の検証・インデックス名の取得で不正な入力は 400

関連Issue / チケット (Related Issues)

  • 非公開(API 台帳とチケットで管理。docs/RULE.md §1 により、番号と修正内容をここで結び付けない)

変更タイプ (Type of Change)

  • 🚀 新機能追加 (Feature)
  • 🐛 バグ修正 (Bug Fix)
  • 🔒 セキュリティ修正 (Security Fix)
  • 🚫 機能のクローズ・非公開化・削除 (Feature Deprecation/Disable)
  • ⚠️ 破壊的変更・データ移行を伴う修正 (Breaking Change / Migration)
  • 📚 仕様書・マニュアル・APIリストの更新 (Documentation)

🤖 0. CI 自動チェック (API Inventory Drift)

PR ごとに WEKO3 コンテナを起動し、url_map のダンプ・台帳との突き合わせ・変更行の
到達可否測定を自動実行する。結果は PR コメントと Actions の artifact
(api-inventory-summary) に出る。

このリポジトリは public のため、台帳もベースラインも同梱していない。
実データはプライベートリポジトリ RCOSDP/weko-secret にあり、CI は Secret 経由で取得する。
以降この文書では、そこを単にプライベートリポジトリと呼ぶ。

台帳はブランチごとに内容が違うため、CI は weko 側と同名のブランチを
プライベートリポジトリから探して使う(head → base → 既定ブランチ の順)。
採用されたブランチ名は PR コメントの冒頭に出るので、件数を読む前にそこを見ること。
対応ブランチが無い場合は既定ブランチと比較され、コメント冒頭に警告が出る。
その件数は当てにならないので、PASS でも「確認済み」と読まないこと。
詳細: tools/api-inventory/ci/README.md §3a

  • CI が PASS している、または FAIL の各項目に対処済み
    • Unit Tests は PASS。API Inventory Drift は PR 作成後に実行中(結果を見て更新する)
      Secret (API_INVENTORY_REPO / API_INVENTORY_SSH_KEY) が未設定のリポジトリ、
      および fork からの PR では、このジョブは何もせずスキップされる。

API を追加・変更した場合(必須)

  • プライベートリポジトリ側の作業ブランチを、この PR のブランチと同名で切った
    • 未作成。経路の追加・削除は無いが、認可の変化を台帳に反映するため、この PR と同名で作成する
      この PR が fix/issue62569 → develop_v2.0.4 なら、プライベート側も
      fix/issue62569 → develop_v2.0.4。同名にしておけば台帳 PR が未マージでも
      CI がそれを見るので、2つの PR のマージ順を気にしなくてよい。
  • プライベートリポジトリの api_snapshot.json を更新し、対応する PR を出した
    • 経路の追加・削除は無い。Drift の結果(認可デコレータの差分)を見て更新する
      export WEKO_API_INVENTORY_DIR=/path/to/weko-secret
      ./install.sh
      python3 tools/api-inventory/scripts/snapshot.py --out "$WEKO_API_INVENTORY_DIR/api_snapshot.json"
      更新しないと CI が落ちる。公開リポジトリのコード変更とは別の PRになる。
  • プライベートリポジトリの weko3_api_list_full.tsv に行を追加・更新し、
    • 行の追加は無い。該当行の所見・対応状況はプライベートリポジトリ側で更新する
      build_checklist.py で 24 列版を再生成した(未収載だと reconcile が FAIL する)
  • 台帳・スナップショット・実測結果をこの公開リポジトリにコミットしていない
    (git status に *.tsv / api_snapshot.json が出ていないこと)

FAIL したときの対処(要約)

まず PR コメント冒頭の台帳ブランチを見る。警告が出ていれば、件数を追う前に
プライベートリポジトリ側の対応ブランチを用意すること(比較相手が違うので件数に意味がない)。

ジョブが落ちる条件は 3 つある。PR コメントのどのセクションに件数が出ているかで切り分ける。

落ちた場所 落ちる条件
ベースラインとの差分 (drift.md) G1〜G7 のいずれかに該当
台帳との突き合わせ (reconcile.md) A + B + C + D + E の合計が 1 件以上
変更行の到達可否測定 (probe) G8 / G9 に該当(結果は artifact に含めないので Actions のログで件数を見る)
検出 意味 対処
G1 / G2 新規経路に認証系デコレータが無い / 認証系デコレータが削除された 実装を直す。意図的な公開なら台帳に根拠を書いてベースライン更新
G3 認証・認可デコレータのコメントアウトが増えた 原則やり直し。残す場合は理由をコード中のコメントに明記する
G4 *_PERMISSION_FACTORY / CSRF 保護 等が危険側の値に変わった 原則やり直し
G5 ModelView の can_delete / can_export が False → True 意図的なら台帳の data_op を更新
G6 静的解析で属性が取れない経路が追加された 台帳に行を追加してレビューする(外部ライブラリ由来など)
G7 依存パッケージの更新で経路が増減した 増えた経路は台帳に追加。消えた経路は行を削除するか allow に登録
G8 台帳で data_op が作成/更新/削除の経路に、未認証で到達した 原則やり直し。意図的な公開なら台帳の根拠を更新。data_op の記載誤りなら台帳を直す
G9 台帳では遮断なのに実測で到達(認可の回帰) 原則やり直し
reconcile A / E 実機にあるが台帳に無い(A: URI 単位 / E: 同一 URI の endpoint 単位) 台帳に行を追加する
reconcile B 台帳にあるが実機の url_map に無い 理由を確認し、正当なら allow に登録する
reconcile C / D メソッド・app 列の記載誤り 台帳を実機に合わせる

reconcile B のうち、実機に存在しないことが正当な行(プラグイン未登録・config で無効等)は
プライベートリポジトリの reconcile_allow.json に理由付きで登録する。理由なしの登録は不可。
登録済みの行は B'(既知・許容)として集計され、E'(endpoint が実機に無い)と併せてゲート対象外になる。

W1〜W6 は WARN でゲートは通るが、レビューでは見ること
(ModelView の追加 / 実装本体の変化 / HTTP メソッド・URL の変化 / 監視対象 config の変化 /
依存パッケージの版の変化)。特に W6(依存の版)は、ベースラインを CI と異なる環境で作ると
毎回出続けて形骸化するため、ベースラインは install.sh で作った環境から生成する。

CI の出力は件数のみ。該当した経路名は Actions には出ないので、プライベートリポジトリ側の
完全版レポート(--summary-only なしで再実行したもの)で確認すること
(このリポジトリは public で、ログ・artifact・PR コメントは誰でも読めるため)。


🔒 1. セキュリティ & API アクセス制御チェック (必須)

認証・認可 (Authentication & Authorization)

  • 新規/変更された Blueprint・View・REST リソースに適切なデコレータ / Permission を設定している
    • 例: @login_required, @pass_record, need(...), Invenio Access Action
    • /api/* では Permission.require(http_exception=403) を使うこと。
      @login_required は API アプリに security.login が無いため 401 ではなく 500 になる
    • CI: G1 / G2 が自動検出(デコレータの有無・削除)
  • 状態変更・破壊的メソッド (POST / PUT / PATCH / DELETE) の権限が正しく制限されている
    • CI: G8 が変更行を未認証で実測(使い捨て環境なので --allow-writes 付きで
      GET / HEAD 以外も叩く)
  • 未ログイン(Anonymous)状態でアクセスした際、意図しないデータ取得・変更が拒絶される
    • CI: G8 / G9 が変更行を実測。ただし測定対象は変更行のみ、かつ既定プロファイルで
      起動した経路のみ。ワークフロー系など未解決プレースホルダの行は skip される
  • 認可を config の permission factory に委ねている場合、None で無効化していない
    • CI: G4 が *_PERMISSION_FACTORY 等を監視

機能クローズ・非公開化の場合 (Feature Disable)

  • UI(画面・ボタン)の非表示だけでなく、バックエンド API(ルーティング・View)も完全に遮断されている
  • 無効化状態で直接 API を叩いた場合、404 Not Found または 403 Forbidden が返ることを確認した

🧪 2. テストコード観点チェック (pytest / Invenio Test Suite)

権限・異常系テスト (Negative & Authorization Tests)

  • 未認証アクセス (Anonymous): トークン/セッションなしのリクエストで 401 Unauthorized または 403 Forbidden / 404 Not Found が返ることを検証するテストがある
  • 権限不足ユーザー (Forbidden): 閲覧権限のみのユーザーが更新/削除 API を叩いた際に 403 になるテストがある
  • 無効化/非公開機能の遮断テスト: 対象機能が無効化されている場合、エンドポイントが 404 / 403 を返すテストがある

境界値・入力バリデーションテスト (Boundary & Validation)

  • 不正なパラメータ(巨大ファイル、異常な MIME タイプ、無効な JSON/XML スキーマ、SQLi/XSS ペイロード等)で適切に 400 Bad Request / バリデーションエラーが返るテストがある

データ整合性・トランザクションテスト (Integrity & Rollback)

  • ファイルストレージ(S3/ローカル)書き込み失敗時や DB エラー時に、中途半端なレコードやゴミファイルが残らずロールバックされるテストがある

🛡️ 3. データ保護 & 破壊的変更防止チェック (Data Safety)

  • 物理削除・上書きの安全性:
    • ファイル・アイテム・メタデータの完全削除/置換処理に、意図しない一括削除や別レコードへの誤適用リスクがない
    • 論理削除、バージョン管理、バックアップ等のロールバック機構が考慮されている
  • トランザクション整合性:
    • DB 更新とストレージ操作がアトミックに管理されている

⚙️ 4. マイグレーション & システム影響チェック (Invenio / WEKO3 Stack)

データベース (DB / Alembic)

  • invenio alembic upgrade(適用)および downgrade(ロールバック)スクリプトを作成・検証した
    • 該当なし(DB の変更なし)
  • 既存データに対する破壊的変更(カラム削除、型変更、NOT NULL 制約追加等)の移行スクリプト/データパッチを用意した

検索インデックス (Elasticsearch / OpenSearch)

  • マッピング定義変更の有無を確認した
    • 確認済み。マッピングの変更なし
  • インデックス再作成(Reindex)やエイリアス切り替え手順を準備・検証した

設定 & 非同期処理 (Config / Celery / Cache)

  • invenio.cfg / 環境変数のデフォルト値を設定した
  • Celery タスクのシグネチャ変更によるキュー滞留・不整合が発生しない
  • キャッシュ(Redis/Memcached)のパージが必要か確認した

📚 5. ドキュメント・仕様書更新チェック (weko-document)

  • API インベントリ: ツールは本リポジトリの tools/api-inventory/、
    台帳・調査記録はプライベートリポジトリ(public リポジトリには置かない)。
    §0 のチェック項目で対応済みなら、ここは確認のみ。
    • エンドポイントの追加・変更・廃止、メソッド、認証・認可要件、リクエスト/レスポンス仕様を更新した
    • 調査記録(weko3_api_auth_findings.md)もプライベートリポジトリに置く。台帳は二重管理しない
  • WEKO3 機能仕様書:
    • 対象機能の仕様追加・変更・クローズ(非公開化)内容を反映した
  • 各種マニュアル (管理者 / 利用者マニュアル):
    • 画面導線・操作手順・権限仕様の変更を反映した
  • 更新不要な場合(理由): API の仕様(経路・パラメータ)は変わらない。応答の変化(403/404/400)と認可の変化は API 台帳(プライベートリポジトリ)で更新する

📋 6. 動作検証エビデンス (Verification Evidence)

テスト実行結果

invenio-resourcesyncserver / invenio-stats / weko-items-ui / weko-search-ui /
weko-signposting / weko-sitemap  修正に関係するテスト passed
invenio-stats  tests/test_views.py  全47件 passed
GitHub Actions Unit Tests  success

CI の成果物 (artifact: api-inventory-summary)

ファイル 内容
drift.md ベースラインとの差分(件数のみ)
reconcile.md 台帳と実機の突き合わせ(件数のみ)

明細(該当した経路名・実測結果)は公開できないため artifact に含めていない。
プライベートリポジトリ側で同じコマンドを --summary-only なしで実行して確認する。

手動で確認したこと

  • 公開アイテムの ResourceSync・サイトマップ・統計表示・signposting が従来どおり出ることは、単体テストでのみ確認。実機での確認は未実施
  • 既知の残り: 非公開のまま削除されたアイテムの ID・日時は changelist に出る(削除前の状態を区別できないため)

🤖 Generated with Claude Code

mhaya and others added 8 commits September 28, 2026 10:30
validate_bibtex で、存在しないレコードと、詳細画面の権限判定
(page_permission_factory) で閲覧できないレコードを、必須項目不足と同じく
出力できないレコードとして返す。
validate_bibtex_export は record_ids がリストでない等の不正な入力に 400 を返す。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- レコード単位の経路に、レコードが公開済み・公開日到来済み・公開インデックス
  配下であることを確認するデコレータ public_record_required を追加
  (判定は weko_records_ui の check_publish_status と invenio_oaiserver の
  is_private_index を再利用)。版付き識別子は親レコードも確認する
- 変更一覧の検索条件に公開状態の絞り込みを追加。削除の通知を維持するため
  削除済みは対象に残す

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
manifest に載せるファイルを、ファイル詳細画面やエクスポートと同じ
check_file_download_permission の判定でダウンロードできるものに限る。
前版のファイルは前版のレコードに対して判定する。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
アイテム詳細画面の非ログイン利用者向けの判定と同じく、check_publish_status
(公開状態・公開日) と check_index_permissions (閲覧可能なインデックス配下) を
満たすアイテムだけを列挙する。判定は最新の状態を持つ親レコードで行う。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
アイテム詳細画面から呼ばれる閲覧数とファイル統計の取得 API に、
詳細画面と同じ閲覧権限(page_permission_factory)を確認するデコレータを付ける。
ファイル統計は bucket を持つレコードで判定する。

あわせて日付指定の POST で本文が不正なときは 400 を返すようにする。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
RECORDS_UI_ENDPOINTS の recid_signposting に、同じ route の recid と同じ
page_permission_factory を permission_factory_imp として設定する。

テストは weko-records-ui が持つ endpoint 定義をそのまま使うようにし、
閲覧できない利用者が拒否されることと、公開アイテム・管理者は引き続き
応答を得られることを確認する。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 閲覧権限の判定には既存の check_index_permissions を使う
- 数字以外を含む指定は 400 を返し、存在しないインデックスは結果に含めない

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- invenio-stats: リクエストの後はフィクスチャのオブジェクトがセッションから
  外れるので、比較に使う id を先に控える
- weko-search-ui: テスト用のインデックスを、フィクスチャのインデックスと
  (parent, position) が重ならない位置に作る

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@sourcery-ai

sourcery-ai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

公開向けの ResourceSync、サイトマップ、統計、signposting、検索 UI、BibTeX 補助 API に、公開状態・公開日時・インデックス閲覧権限・ファイル取得権限の検証を追加し、非公開情報の露出を防ぎながら入力・認可・不存在時のHTTP応答を整理している。

Sequence diagram for permission-filtered public APIs

sequenceDiagram
    actor Client
    participant API as Public API
    participant Record as WekoRecord
    participant Permission as Permission checks

    Client->>API: Request resource, sitemap, stats, or BibTeX data
    API->>Record: Load record
    API->>Permission: Check publication status and page permission
    alt Invalid input
        API-->>Client: 400 Bad Request
    else Record not found
        API-->>Client: 404 Not Found
    else Record not viewable
        API-->>Client: 403 Forbidden or omit item
    else Public and viewable
        API-->>Client: Return data or public URL
    end
Loading

Sequence diagram for permission-filtered ResourceSync files

sequenceDiagram
    participant Client
    participant ResourceSync
    participant Record as WekoRecord
    participant FilePermission as check_download_file

    Client->>ResourceSync: Request public record manifest or file content
    ResourceSync->>Record: Load record by record_id
    ResourceSync->>ResourceSync: is_public_record(record_id)
    alt Record is not public
        ResourceSync-->>Client: 404 Not Found
    else Record is public
        loop Record files
            ResourceSync->>FilePermission: can_download_file(record, file)
            alt File is downloadable
                ResourceSync-->>Client: Include file in manifest or archive
            else File is not downloadable
                ResourceSync->>ResourceSync: Omit file
            end
        end
    end
Loading

Flow diagram for public-item filtering

flowchart TD
    A[Candidate record or index] --> B{Published and publication date reached?}
    B -- No --> X[Exclude from public output]
    B -- Yes --> C{Public index and browse permission granted?}
    C -- No --> X
    C -- Yes --> D[Expose in ResourceSync, sitemap, search UI, or signposting]
    D --> E{File requested or listed?}
    E -- Yes --> F{check_file_download_permission}
    F -- Allowed --> G[Include file]
    F -- Denied --> X
    E -- No --> H[Return public record metadata]
Loading

File-Level Changes

Change Details Files
ResourceSync の公開レコード・ダウンロード可能ファイルだけを配信対象にした
  • レコード単位の各配信エンドポイントで公開状態、公開日時、公開インデックス、バージョン親レコードを検証し、非公開対象を404にする
  • changelist/changedump の検索対象を公開・削除済みに限定し、削除通知は維持する
  • manifest と変更 manifest から閲覧者がダウンロードできないファイルを除外する
modules/invenio-resourcesyncserver/invenio_resourcesyncserver/api.py
modules/invenio-resourcesyncserver/invenio_resourcesyncserver/permissions.py
modules/invenio-resourcesyncserver/invenio_resourcesyncserver/query.py
modules/invenio-resourcesyncserver/invenio_resourcesyncserver/views.py
modules/invenio-resourcesyncserver/tests/test_api.py
modules/invenio-resourcesyncserver/tests/test_permissions.py
modules/invenio-resourcesyncserver/tests/test_query.py
modules/invenio-resourcesyncserver/tests/test_views.py
公開補助 API の列挙・閲覧結果を、実際の公開状態と閲覧権限に整合させた
  • サイトマップ生成時に公開済み・公開日時到来・閲覧可能なアイテムだけを列挙する
  • 検索 UI のパンくず用インデックス名から、存在しないまたは閲覧できないインデックスを除外し、入力形式を400で検証する
  • signposting に詳細画面と同じ page_permission_factory を設定する
modules/weko-sitemap/weko_sitemap/ext.py
modules/weko-sitemap/tests/test_ext.py
modules/weko-search-ui/weko_search_ui/views.py
modules/weko-search-ui/tests/test_views.py
modules/weko-records-ui/weko_records_ui/config.py
modules/weko-signposting/tests/conftest.py
modules/weko-signposting/tests/test_api.py
統計 API にレコード単位の詳細画面権限チェックと入力・存在性の明確なエラー処理を追加した
  • レコード統計とファイル統計の GET/POST に page_permission_factory による認可を適用する
  • 不正なUUID・日付入力を400、存在しないレコードまたはバケットを404、権限不足を403として返す
  • バケットから所有レコードを解決して閲覧権限を判定する
modules/invenio-stats/invenio_stats/permissions.py
modules/invenio-stats/invenio_stats/views.py
modules/invenio-stats/tests/conftest.py
modules/invenio-stats/tests/test_views.py
BibTeX 検証で閲覧不能レコードを存在しないレコードと同じ無効入力として扱った
  • レコード取得失敗または詳細画面権限不足のIDをシリアライズせず無効IDに追加する
  • リクエストJSONとrecord_ids配列の型・要素を検証し、不正入力を400で返す
  • 管理者による非公開レコードの検証は引き続き許可する
modules/weko-items-ui/weko_items_ui/utils.py
modules/weko-items-ui/weko_items_ui/views.py
modules/weko-items-ui/tests/test_utils.py
modules/weko-items-ui/tests/test_views.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Sep 29, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: f030858f-3b8a-4f69-9784-fe342a368183

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

🔍 Claude レビュー統合

指摘はありません。


次にすること: 外部データは bot の要約と、レビューをスキップした旨の通知だけで、裁定すべき指摘スレッドはありませんでした。自分でも重大な問題は見つかりませんでした。ResourceSync のファイル本体の配信は _export_item 側で既に check_file_download_permission が掛かっています。追加された PIDDoesNotExistError と NoResultFound の import、page_permission_factory、check_index_permissions、is_private_index のシグネチャも確認しました。作者は scripts/ci/run-local.sh で対象モジュールを回して合否を確認してください。


モデル sonnet / 2 回実行して和集合 / コスト $0.4581。同じ入力でも結果が揺れるため複数回まわし、一部のパスでしか挙がらなかったものには回数を添えています

他レビューを踏まえた自動レビューです。誤りが含まれることがあります。

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="modules/weko-sitemap/weko_sitemap/ext.py" line_range="121-123" />
<code_context>
              .limit(current_app.config['WEKO_SITEMAP_TOTAL_MAX_URL_COUNT']))

         for recid, rm in q.yield_per(1000):
+            pid_value = (recid.pid_value).replace('.1', '')
+            if not self._is_public_item(pid_value):
+                continue
             yield {
                 'loc': url_for('invenio_records_ui.recid',
</code_context>
<issue_to_address>
**issue (bug_risk):** The query applies `WEKO_SITEMAP_TOTAL_MAX_URL_COUNT` before `_is_public_item` filters records, so when the first page contains enough private, future-dated, or inaccessible records, later public records are never examined and are omitted from the sitemap.

**Triggers:** When the sitemap reaches its configured maximum candidate count and inaccessible records occur before public records in PID order.

**Suggested fix:** Filter public records in the query or continue scanning until the generator has yielded the configured maximum number of public URLs.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and these changes alter authorization decisions across public resource-sync downloads, statistics, BibTeX export, signposting, and sitemap generation; a faulty check could expose unpublished records, files, or usage data to unauthorized callers across the affected APIs. Reverting stops future exposure, but any data already disclosed cannot be recalled, and a mistaken permission policy could affect everyone immediately without a reliable failure signal.

Blocking findings: modules/weko-sitemap/weko_sitemap/ext.py:123


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment on lines +121 to +123
pid_value = (recid.pid_value).replace('.1', '')
if not self._is_public_item(pid_value):
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The query applies WEKO_SITEMAP_TOTAL_MAX_URL_COUNT before _is_public_item filters records, so when the first page contains enough private, future-dated, or inaccessible records, later public records are never examined and are omitted from the sitemap.

Triggers: When the sitemap reaches its configured maximum candidate count and inaccessible records occur before public records in PID order.

Suggested fix: Filter public records in the query or continue scanning until the generator has yielded the configured maximum number of public URLs.

@github-actions

Copy link
Copy Markdown

API インベントリ差分(件数のみ)

台帳ブランチ: develop_v2.1.0

明細は公開できないため件数のみ表示しています。該当箇所はプライベートリポジトリ側の台帳・レポートで確認してください。

ベースラインとの差分

API インベントリ差分レポート

  • 旧: b572c442d v2.0.4-577-gb572c442d (profile=default) endpoints=933 (外部ライブラリ由来 359)
  • 新: 8c78132f3 v2.0.4-617-g8c78132f3 (profile=default) endpoints=933 (外部ライブラリ由来 359)

判定: ✅ PASS (FAIL 0 / WARN 1)

サマリ

分類 件数
ADDED 0
REMOVED 0
RULE_CHANGED 0
METHODS_CHANGED 0
AUTH_CHANGED 4
IMPL_CHANGED 2
ATTRS_UNKNOWN_NEW 0
ModelView 追加 0
ModelView 削除 0
ModelView フラグ変化 0
config 変化 0
コメントアウト認証の増加 0
依存パッケージの版変化 0

[WARN] W2 実装本体が変化(data_op / 情報露出を再確認) — 2件

件数のみ。該当の経路名はプライベートリポジトリ側の完全版レポートを参照。


台帳との突き合わせ

スナップショット ↔ インベントリ 突き合わせ

  • リビジョン: 8c78132f3 v2.0.4-617-g8c78132f3 経路URI=913
  • 台帳: 行=1053 URI=924

件数のみ。詳細はプライベートリポジトリ側の完全版レポートを参照。

判定: ✅ 一致 (0件)

検出 件数
A. インベントリ未収載(抽出漏れ) 0
B. 実機に無い(未説明) 0
B'. 実機に無い(既知・許容) 11
C. メソッド不一致 0
D. app列の不一致 0
E. endpoint 未収載 0
E'. endpoint が実機に無い(参考) 1

ソース由来の経路検知

ソース由来の経路検知

  • 解析対象: /home/runner/work/weko/weko
検知源 件数
route 286
expose 206
add_url_rule 75
rest_config 28
modelview 23
entry_point 115
計 733

判定: ✅ 全検知が台帳に対応 (0件)

検知源 検知 台帳に対応 未収載 既知・許容
route 286 286 0 0
expose 206 206 0 0
add_url_rule 75 71 0 0
rest_config 28 26 0 2
modelview 23 23 0 0
entry_point 115 115 0 0

add_url_rule(**rule) 形式の config 駆動一括登録が 4 箇所。個々の経路は rest_config 側で検知する。

参考: 静的検知と結びつかなかった台帳行

  • 全体: 170 / 1053 行
  • うち実ファイルを持つ行: 24(pip・framework・ModelView 総称表記を除いた数)

record = WekoRecord.get_record_by_pid(record_id)
if record:
for file in record.files:
if not can_download_file(record, file):

@ivis-miyachi ivis-miyachi Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no.102

prev_record = None
if current_record:
list_file = [file for file in current_record.files]
list_file = [

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no.107

prev_checksum = []
if prev_record:
list_file.extend([file for file in prev_record.files])
list_file.extend(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no.107

prev_record.files
]
for file in list_file:
for record, file in list_file:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no.107

"post_filter": {
"bool": {
"must": [
{

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no.100, 102, 104, 106, 107, 108

@@ -0,0 +1,87 @@
# -*- coding: utf-8 -*-

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no.100, 102, 104, 106, 107, 108



@blueprint.route("/resync/<index_id>/<record_id>/file_content.zip")
@public_record_required()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no.100



@blueprint.route("/resync/<index_id>/<record_id>/resourcedump_manifest.xml")
@public_record_required()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no.102



@blueprint.route("/resync/<index_id>/<record_id>/changedump_manifest.xml")
@public_record_required()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no.107



@blueprint.route("/resync/<index_id>/<record_id>/change_dump_content.zip")
@public_record_required()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no.108


stats_api_access = action_factory('stats-api-access')
stats_api_permission = Permission(stats_api_access)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no.113,114,115,116

return wrapper


def get_query_date(data):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no.113,114,115,116


return result

@record_view_permission_required

@ivis-miyachi ivis-miyachi Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no113

abort(400)
return self.make_response(self.get_data(record_uuid, get_period=True))

@record_view_permission_required

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no.114

else:
date = d['date']
except (TypeError, ValueError):
except ValueError:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no.114

except ValueError:
current_app.logger.error(traceback.format_exc())
abort(400)
date = get_query_date(request.get_json(force=False, silent=True))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no.114


return result

@bucket_view_permission_required

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no115

file_key,
get_period=True))

@bucket_view_permission_required

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no116

date = None
else:
date = d['date']
date = get_query_date(request.get_json(force=False, silent=True))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no 116

from weko_schema_ui.serializers import WekoBibTexSerializer
for record_id in record_ids:
record = WekoRecord.get_record_by_pid(record_id)
try:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no.398

from .utils import validate_bibtex
post_data = request.get_json()
record_ids = post_data['record_ids']
post_data = request.get_json(silent=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no.398

pid_type='recid',
route='/records/<pid_value>',
view_imp='weko_signposting.api.requested_signposting',
permission_factory_imp='weko_records_ui.permissions'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no.504

@blueprint.route("/get_path_name_dict/<string:path_str>", methods=["GET"])
def get_path_name_dict(path_str=""):
"""Get path and name."""
"""Get path and name.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no.560

from weko_index_tree.utils import check_index_permissions
path_name_dict = {}
path_arr = path_str.split("_")
if not all(re.match(r"^[0-9]{1,18}$", path) for path in path_arr):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no.560

abort(400)
for path in path_arr:
index = Indexes.get_index(index_id=path)
index = Indexes.get_index(index_id=int(path))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no.560

continue
idx_name = index.index_name
idx_name_en = index.index_name_english
idx_name_en = index.index_name_english or ""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no.560

.limit(current_app.config['WEKO_SITEMAP_TOTAL_MAX_URL_COUNT']))

for recid, rm in q.yield_per(1000):
pid_value = (recid.pid_value).replace('.1', '')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no.568

'loc': url_for('invenio_records_ui.recid',
pid_value=(recid.pid_value).replace(
'.1', ''),
pid_value=pid_value,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no.568

rm.updated, 'yyyy-MM-ddTHH:mm:ssz', 'full')
}

@staticmethod

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix no.568

@ivis-miyachi
ivis-miyachi merged commit be0abe2 into develop_v2.1.0 Sep 29, 2026
218 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants