Skip to content

Hotfix v0.9.26 hiroba from w2026 64 hiroba - #1945

Closed
kanon-yoneji wants to merge 16 commits into
RCOSDP:v0.9.26_hiroba_developfrom
ivis-weko3-dev:hotfix-v0.9.26_hiroba_from_W2026-64_hiroba
Closed

kanon-yoneji wants to merge 16 commits into
RCOSDP:v0.9.26_hiroba_developfrom
ivis-weko3-dev:hotfix-v0.9.26_hiroba_from_W2026-64_hiroba

Conversation

@kanon-yoneji

@kanon-yoneji kanon-yoneji commented Oct 23, 2026 •

Copy link
Copy Markdown

概要 (Summary)

  • #63646
  • #63830
  • #63219

Summary by Sourcery

Harden access control and billing flows while migrating the Shibboleth login proxy from PHP to Python.

New Features:

  • Validate billing prices server-side and prompt users to reconfirm when a file price changes before payment.
  • Replace the Shibboleth PHP login proxy with a Python CGI implementation.

Bug Fixes:

  • Enforce item permissions on search-data API access.
  • Update Shibboleth login redirects to use the Python login endpoint.

Enhancements:

  • Standardize Babel template extraction settings and enable the required Jinja extensions across modules.

Build:

  • Replace PHP-FPM dependencies with fcgiwrap and Python requests in the Nginx image.

Deployment:

  • Update Nginx and supervisor configuration to serve the Python Shibboleth login proxy through fcgiwrap.

Tests:

  • Expand search-data API tests to cover unauthenticated and role-based access behavior.
  • Update account login redirect tests for the Python login endpoint.

kanon-yoneji and others added 16 commits May 8, 2026 15:32
W2025-33_early(release: 2026/03/30) from V0.9.26 hiroba develop
Update get_search_data API access control and tests(#63219)
…rom_W2026-64_hiroba

fix charge process to use server-calculated price and handle price changes(#63646)
…vert_php_to_py

Convert PHP files to Python and update related configuration(#63830)
@sourcery-ai

sourcery-ai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

This hotfix standardizes template translation extraction, tightens search API permissions, migrates the Shibboleth login proxy from PHP-FPM to Python/fcgiwrap, and prevents paid-file charges from using stale or client-tampered prices by validating billing details server-side and prompting for updated prices.

Sequence diagram for server-validated paid-file charging

sequenceDiagram
    participant User
    participant Browser
    participant ChargeAPI
    participant Billing
    participant Payment

    User->>Browser: handleChargeBillingFile
    Browser->>ChargeAPI: GET /charge with item_id, file_name, price
    ChargeAPI->>Billing: get_file_charge_info
    Billing-->>ChargeAPI: status, server_price, unit
    alt price differs
        ChargeAPI-->>Browser: price_changed with price and unit
        Browser->>Browser: removeChargeItem
        Browser->>Browser: showChargeConfirmation
    else price matches
        ChargeAPI->>Payment: create_charge
        Payment-->>ChargeAPI: redirect URL or payment status
        ChargeAPI-->>Browser: charge result
    end
Loading

Sequence diagram for Shibboleth login proxy

sequenceDiagram
    participant User
    participant Nginx
    participant LoginProxy
    participant WEKO
    participant Shibboleth

    User->>Nginx: GET /secure/login.py
    Nginx->>LoginProxy: fcgiwrap request
    LoginProxy->>LoginProxy: Validate affiliation headers
    LoginProxy->>WEKO: POST /weko/shib/login with attributes
    WEKO->>Shibboleth: Process login
    Shibboleth-->>WEKO: Redirect or login page
    WEKO-->>LoginProxy: Response
    LoginProxy-->>Nginx: HTTP redirect or HTML
    Nginx-->>User: Login response
Loading

File-Level Changes

Change Details Files
Standardize Babel/Jinja2 extraction settings across modules and enable the full extension set.
  • Add explicit non-silent extraction configuration.
  • Correct the misspelled silent option in affected WEKO modules.
  • Add webassets, loop controls, i18n, and do extensions consistently.
modules/cookiecutter-weko-module/{{ cookiecutter.project_shortname }}/babel.ini
modules/invenio-accounts/babel.ini
modules/invenio-communities/babel.ini
modules/invenio-deposit/babel.ini
modules/invenio-files-rest/babel.ini
modules/invenio-mail/babel.ini
modules/invenio-oaiharvester/babel.ini
modules/invenio-oaiserver/babel.ini
modules/invenio-oauth2server/babel.ini
modules/invenio-previewer/babel.ini
modules/invenio-records/babel.ini
modules/invenio-resourcesyncclient/babel.ini
modules/invenio-resourcesyncserver/babel.ini
modules/weko-accounts/babel.ini
modules/weko-admin/babel.ini
modules/weko-authors/babel.ini
modules/weko-bulkupdate/babel.ini
modules/weko-deposit/babel.ini
modules/weko-gridlayout/babel.ini
modules/weko-groups/babel.ini
modules/weko-handle/babel.ini
modules/weko-index-tree/babel.ini
modules/weko-indextree-journal/babel.ini
modules/weko-items-autofill/babel.ini
modules/weko-items-ui/babel.ini
modules/weko-itemtypes-ui/babel.ini
modules/weko-logging/babel.ini
modules/weko-plugins/babel.ini
modules/weko-records-ui/babel.ini
modules/weko-schema-ui/babel.ini
modules/weko-search-ui/babel.ini
modules/weko-sitemap/babel.ini
modules/weko-swordserver/babel.ini
modules/weko-theme/babel.ini
modules/weko-user-profiles/babel.ini
modules/weko-workflow/babel.ini
Strengthen access control for the search-data API and update its tests for authenticated and role-based behavior.
  • Require login and item permission for get_search_data.
  • Verify unauthenticated redirects and expected 200/403 responses across user roles.
  • Update successful-result expectations to exclude unauthorized contributor entries.
modules/weko-items-ui/weko_items_ui/views.py
modules/weko-items-ui/tests/test_views.py
Replace the Shibboleth PHP login proxy with a Python CGI implementation and reconfigure the nginx container to run it through fcgiwrap.
  • Install Python CGI dependencies and remove PHP-FPM setup.
  • Post Shibboleth attributes to the WEKO login endpoint and relay redirects or login responses.
  • Switch login URLs, FastCGI socket, and script configuration from PHP to Python.
nginx/Dockerfile
nginx/Dockerfile.arm64
nginx/index.php
nginx/login.php
nginx/login.py
nginx/phpinfo.php
nginx/supervisord.conf
nginx/weko.conf
modules/weko-accounts/weko_accounts/config.py
modules/weko-accounts/tests/test_views.py
scripts/instance.cfg
Make paid-file charging server-authoritative and handle price changes safely in the UI.
  • Resolve and validate the billable file and calculate its current server-side price before creating a charge.
  • Reject invalid, already-paid, non-billable, and price-mismatched requests without charging.
  • Return updated pricing for mismatches and reopen a confirmation dialog after removing the pending client-side charge item.
  • Centralize session-storage cleanup and use jQuery parameter encoding for charge requests.
modules/weko-records-ui/weko_records_ui/permissions.py
modules/weko-records-ui/weko_records_ui/views.py
modules/weko-records-ui/weko_records_ui/static/js/weko_records_ui/app.js
modules/weko-records-ui/weko_records_ui/templates/weko_records_ui/modal_charge_confirmation.html
modules/weko-records-ui/weko_records_ui/translations/en/LC_MESSAGES/messages.mo
modules/weko-records-ui/weko_records_ui/translations/en/LC_MESSAGES/messages.po
modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.mo
modules/weko-records-ui/weko_records_ui/translations/ja/LC_MESSAGES/messages.po
modules/weko-records-ui/weko_records_ui/translations/messages.pot

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 Oct 23, 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
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 635eb510-3dfc-4d41-8f68-7c6cbcaa7cd5

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
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@kanon-yoneji
kanon-yoneji deleted the hotfix-v0.9.26_hiroba_from_W2026-64_hiroba branch October 23, 2026 08:31

@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 3 security issues, and 4 other issues

Security issues:

  • User controlled data in methods like innerHTML, outerHTML or document.write is an anti-pattern that can lead to XSS vulnerabilities (link)
  • User controlled data in a textarea.innerHTML is an anti-pattern that can lead to XSS vulnerabilities (link)
  • Certificate verification has been explicitly disabled. This permits insecure connections to insecure servers. Re-enable certification validation. (link)

Fixed security issues:

  • SSL verification is disabled but should not be (currently CURLOPT_SSL_VERIFYPEER= false) (link)
  • The 'phpinfo' function may reveal sensitive information about your environment. (link)
Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="modules/weko-records-ui/weko_records_ui/permissions.py" line_range="647" />
<code_context>
+            return 'already', None, None
+        return 'not_required', None, None
+
+    price, unit = get_file_price(record['_deposit']['id'])
+    if price is None or price <= 0:
+        return 'invalid', None, None
</code_context>
<issue_to_address>
**Users can pay for inaccessible files**

When a billed file has `open_no` or `open_restricted` access and the caller has a role with an item billing price, for a billed file with `open_no` or `open_restricted` access, `check_file_download_permission` returns false, and `get_file_charge_info` treats that denial as a reason to return a price. `/charge` can then create a payment even though the permission check still denies the user the file afterward.

Only return `ok` for access modes that support billing as an access grant; reject files whose access policy cannot be satisfied by payment.
</issue_to_address>

### Comment 2
<location path="modules/weko-records-ui/weko_records_ui/views.py" line_range="1071-1074" />
<code_context>
+            price (int) : 課金額(税込)。status が ok 以外の場合は None
+            unit  (str) : 通貨単位。status が ok 以外の場合は None
+    """
+    try:
+        record = WekoRecord.get_record_by_pid(item_id)
+    except PIDDoesNotExistError:
</code_context>
<issue_to_address>
**Concurrent charges create duplicate reservations**

When two charge requests for the same user overlap before either writes the Redis key, both requests pass the checks and create payment reservations, then overwrite the same cache key; a callback can use the other request’s item ID, leaving the other callback without a key. The user can receive two 3DS flows and be charged twice.

Atomically claim or lock the user-and-item charge before creating a reservation, and prevent a concurrent request from creating another one.

Also at `modules/weko-records-ui/weko_records_ui/views.py:1136`.
</issue_to_address>

### Comment 3
<location path="modules/weko-records-ui/weko_records_ui/views.py" line_range="1133-1140" />
<code_context>
     # 課金予約
     try:
-        redirect_url = create_charge(current_user.id, int(item_id), price, title, file_url, ret_url)
+        redirect_url = create_charge(
+            current_user.id,
+            int(item_id),
+            server_price,
+            title,
+            file_url,
+            ret_url
+        )
         if redirect_url in ['connection_error', 'api_error']:
             # 課金失敗
</code_context>
<issue_to_address>
**Failed requests orphan payment reservations**

When the application process crashes or the cache write fails after the payment provider accepts the reservation, `create_charge()` can create an external reservation and return its redirect URL before `datastore.put()` records the item ID. If the process fails in between, the response never gives the user the 3DS URL and the callback has no corresponding cache state; retrying starts another reservation instead of recovering the first.

Persist a pending charge record and its provider reservation identifier before returning the redirect, and make retries recover or safely cancel that pending reservation.
</issue_to_address>

### Comment 4
<location path="modules/weko-records-ui/weko_records_ui/views.py" line_range="1086-1087" />
<code_context>
+    if charge_status != 'ok':
+        # not_required / invalid は通常の画面操作では起きないため、専用の応答は設けない
+        current_app.logger.warning(
+            f'Charge rejected ({charge_status}): user: {current_user.id}, '
+            f'item_id: {item_id}, file_name: {file_name}')
+        return jsonify({'status': 'error'})
+    if int(price) != server_price:
</code_context>
<issue_to_address>
**Forged charge log entries**

When a caller sends a charge request with a newline in `file_name` that reaches the rejection warning, `file_name` comes directly from the request and is interpolated into the warning without escaping. A caller can include newline characters to forge additional log entries, undermining log review and alerting.

Remove or escape control characters in request values before writing them to logs, or use structured logging that preserves field boundaries.
</issue_to_address>

### Comment 5
<location path="modules/weko-records-ui/weko_records_ui/static/js/weko_records_ui/app.js" line_range="390" />
<code_context>
        textarea.innerHTML = String(value);
</code_context>
<issue_to_address>
**security (javascript.browser.security.insecure-document-method):** User controlled data in methods like `innerHTML`, `outerHTML` or `document.write` is an anti-pattern that can lead to XSS vulnerabilities

*Source: opengrep*
</issue_to_address>

### Comment 6
<location path="modules/weko-records-ui/weko_records_ui/static/js/weko_records_ui/app.js" line_range="390" />
<code_context>
        textarea.innerHTML = String(value);
</code_context>
<issue_to_address>
**security (javascript.browser.security.insecure-innerhtml):** User controlled data in a `textarea.innerHTML` is an anti-pattern that can lead to XSS vulnerabilities

*Source: opengrep*
</issue_to_address>

### Comment 7
<location path="nginx/login.py" line_range="50" />
<code_context>
    response = requests.post(url, data=data, verify=False, cookies=cookie_jar)
</code_context>
<issue_to_address>
**security (python.requests.security.disabled-cert-validation):** Certificate verification has been explicitly disabled. This permits insecure connections to insecure servers. Re-enable certification validation.

*Source: opengrep*
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 7 findings to address first, and if the server-side file and price checks are wrong, the payment flow can charge a user an unintended amount; reverting the code does not undo a completed charge. The new CGI login proxy also changes how Shibboleth attributes are forwarded, so an authentication regression could expose access until fixed.

Blocking findings: modules/weko-records-ui/weko_records_ui/permissions.py:647, modules/weko-records-ui/weko_records_ui/views.py:1074, modules/weko-records-ui/weko_records_ui/views.py:1140, modules/weko-records-ui/weko_records_ui/views.py:1087, modules/weko-records-ui/weko_records_ui/static/js/weko_records_ui/app.js:390, and 2 more


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

return 'already', None, None
return 'not_required', None, None

price, unit = get_file_price(record['_deposit']['id'])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Users can pay for inaccessible files

When a billed file has open_no or open_restricted access and the caller has a role with an item billing price, for a billed file with open_no or open_restricted access, check_file_download_permission returns false, and get_file_charge_info treats that denial as a reason to return a price. /charge can then create a payment even though the permission check still denies the user the file afterward.

Only return ok for access modes that support billing as an access grant; reject files whose access policy cannot be satisfied by payment.

Prompt for AI agents
In `modules/weko-records-ui/weko_records_ui/permissions.py` at line 647:

**Users can pay for inaccessible files**

When a billed file has `open_no` or `open_restricted` access and the caller has a role with an item billing price, for a billed file with `open_no` or `open_restricted` access, `check_file_download_permission` returns false, and `get_file_charge_info` treats that denial as a reason to return a price. `/charge` can then create a payment even though the permission check still denies the user the file afterward.

Only return `ok` for access modes that support billing as an access grant; reject files whose access policy cannot be satisfied by payment.

Comment on lines +1071 to +1074
try:
charge_status, server_price, unit = get_file_charge_info(
int(item_id), file_name
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟠 High · Concurrent charges create duplicate reservations

When two charge requests for the same user overlap before either writes the Redis key, both requests pass the checks and create payment reservations, then overwrite the same cache key; a callback can use the other request’s item ID, leaving the other callback without a key. The user can receive two 3DS flows and be charged twice.

Atomically claim or lock the user-and-item charge before creating a reservation, and prevent a concurrent request from creating another one.

Also at modules/weko-records-ui/weko_records_ui/views.py:1136.

Prompt for AI agents
In `modules/weko-records-ui/weko_records_ui/views.py` at lines 1071-1074:

**Concurrent charges create duplicate reservations**

When two charge requests for the same user overlap before either writes the Redis key, both requests pass the checks and create payment reservations, then overwrite the same cache key; a callback can use the other request’s item ID, leaving the other callback without a key. The user can receive two 3DS flows and be charged twice.

Atomically claim or lock the user-and-item charge before creating a reservation, and prevent a concurrent request from creating another one.

Also at `modules/weko-records-ui/weko_records_ui/views.py:1136`.

Comment on lines +1133 to +1140
redirect_url = create_charge(
current_user.id,
int(item_id),
server_price,
title,
file_url,
ret_url
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Failed requests orphan payment reservations

When the application process crashes or the cache write fails after the payment provider accepts the reservation, create_charge() can create an external reservation and return its redirect URL before datastore.put() records the item ID. If the process fails in between, the response never gives the user the 3DS URL and the callback has no corresponding cache state; retrying starts another reservation instead of recovering the first.

Persist a pending charge record and its provider reservation identifier before returning the redirect, and make retries recover or safely cancel that pending reservation.

Prompt for AI agents
In `modules/weko-records-ui/weko_records_ui/views.py` at lines 1133-1140:

**Failed requests orphan payment reservations**

When the application process crashes or the cache write fails after the payment provider accepts the reservation, `create_charge()` can create an external reservation and return its redirect URL before `datastore.put()` records the item ID. If the process fails in between, the response never gives the user the 3DS URL and the callback has no corresponding cache state; retrying starts another reservation instead of recovering the first.

Persist a pending charge record and its provider reservation identifier before returning the redirect, and make retries recover or safely cancel that pending reservation.

Comment on lines +1086 to +1087
f'Charge rejected ({charge_status}): user: {current_user.id}, '
f'item_id: {item_id}, file_name: {file_name}')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium · Forged charge log entries

When a caller sends a charge request with a newline in file_name that reaches the rejection warning, file_name comes directly from the request and is interpolated into the warning without escaping. A caller can include newline characters to forge additional log entries, undermining log review and alerting.

Remove or escape control characters in request values before writing them to logs, or use structured logging that preserves field boundaries.

Prompt for AI agents
In `modules/weko-records-ui/weko_records_ui/views.py` at lines 1086-1087:

**Forged charge log entries**

When a caller sends a charge request with a newline in `file_name` that reaches the rejection warning, `file_name` comes directly from the request and is interpolated into the warning without escaping. A caller can include newline characters to forge additional log entries, undermining log review and alerting.

Remove or escape control characters in request values before writing them to logs, or use structured logging that preserves field boundaries.

function showChargeConfirmation(price, unit, priceChanged, previousPrice, previousUnit) {
const getDisplayUnit = function (value) {
const textarea = document.createElement('textarea');
textarea.innerHTML = String(value);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security (javascript.browser.security.insecure-document-method): User controlled data in methods like innerHTML, outerHTML or document.write is an anti-pattern that can lead to XSS vulnerabilities

Source: opengrep

function showChargeConfirmation(price, unit, priceChanged, previousPrice, previousUnit) {
const getDisplayUnit = function (value) {
const textarea = document.createElement('textarea');
textarea.innerHTML = String(value);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security (javascript.browser.security.insecure-innerhtml): User controlled data in a textarea.innerHTML is an anti-pattern that can lead to XSS vulnerabilities

Source: opengrep

Comment thread nginx/login.py
cookie_jar = cookiejar.LWPCookieJar(temp_path)

# Request to the Shibboleth login
response = requests.post(url, data=data, verify=False, cookies=cookie_jar)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security (python.requests.security.disabled-cert-validation): Certificate verification has been explicitly disabled. This permits insecure connections to insecure servers. Re-enable certification validation.

Source: opengrep

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.

4 participants