Skip to content

[MISC] Preserve view function identity in platform-service auth middleware#2160

Merged
chandrasekharan-zipstack merged 1 commit into
mainfrom
fix/platform-service-auth-middleware-wraps
Jul 10, 2026
Merged

[MISC] Preserve view function identity in platform-service auth middleware#2160
chandrasekharan-zipstack merged 1 commit into
mainfrom
fix/platform-service-auth-middleware-wraps

Conversation

@chandrasekharan-zipstack

@chandrasekharan-zipstack chandrasekharan-zipstack commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

What

Adds functools.wraps to authentication_middleware in platform-service, and drops the six now-redundant endpoint="..." kwargs it was forcing onto @platform_bp.route(...).

Follow-up to the discussion on #1964. That PR fixed a decorator-order bug in prompt-service (auth stacked above route, so auth never ran). That bug does not exist in platform-service — all its routes already have @route on top. But a sibling defect in the same middleware does.

Why

authentication_middleware returned a bare closure:

def authentication_middleware(func):
    def wrapper(*args, **kwargs):
        ...
    return wrapper          # __name__ == "wrapper" for every route

Flask derives an endpoint name from the view function's __name__. So every authenticated view registered as wrapper. Consequences:

  • Six routes carried an endpoint="..." kwarg whose only purpose was to dodge the resulting collision — each value already identical to its function name.
  • /usage was the one authenticated route without that kwarg, so it registered as endpoint platform.wrapper. It worked only because it was the last one left defaulting.
  • Adding any new authenticated route without endpoint= fails at import with AssertionError: View function mapping is overwriting an existing endpoint function: platform.wrapper. A landmine, not a live bug.
  • Handler docstrings and signatures were lost on every view.

Reproduced on main before the fix:

platform.page_usage       /page-usage
platform.platform_details /platform_details
...
platform.wrapper          /usage        <-- here

x2text-service's equivalent middleware already patches this by hand (wrapper.__name__ = func.__name__, app/authentication_middleware.py:19). This brings platform-service in line, using the stdlib decorator rather than a manual assignment.

How

File Change
controller/platform.py @functools.wraps(func) on the middleware's wrapper; drop 6 redundant endpoint= kwargs
tests/test_route_endpoints.py new — one test guarding endpoint naming
tests/test_auth_middleware.py restored, hermetic (see below)
tests/conftest.py new — sets the env vars platform_service.env demands at import

Can this PR break any existing features. If yes, please list possible items. If no, please explain why.

No.

URL paths and methods are byte-identical before and after — verified by dumping app.url_map on both sides. Only Flask's internal endpoint registry key changes, and only for one route: platform.wrapperplatform.usage.

An endpoint name is what url_for() and app.view_functions[...] look up. It is not the URL.

  • There are zero references to url_for, view_functions, or url_map anywhere in the repo (venvs excluded). Nothing resolves a route by endpoint name.
  • The external caller of /usage is unstract/sdk1/src/unstract/sdk1/audit.py:128url = f"{base_url}/usage", a plain HTTP path. Backend and workers reach platform-service over HTTP by path, never by endpoint name.
  • The v1/usage/ hits in backend/ and workers/ are Django's own REST APIs, a different service — unrelated to this Flask route.

Auth enforcement itself is unchanged: these routes were already correctly ordered and already authenticated.

Database Migrations

N/A

Env Config

N/A

Relevant Docs

N/A

Related Issues or PRs

Follow-up to #1964 (which targets prompt-service). Rebased on top of #2115 / #2151.

Dependencies Versions

N/A

Notes on Testing

Runs as the rig's existing unit-platform-service group (tests/groups.yaml) — no new group needed:

$ uv run python -m tests.rig run unit-platform-service
15 passed in 2.68s

test_route_endpoints.py — a single test that registers the real platform_bp and asserts no endpoint is named platform.wrapper. Verified as a real regression test: with @functools.wraps removed it fails with exactly the collision this PR prevents —

AssertionError: View function mapping is overwriting an existing endpoint function: platform.wrapper

test_auth_middleware.py#2115 deleted this file because it imported unstract.platform_service.main (removed) and get_account_from_bearer_token (removed), and made live Postgres calls. That deletion was correct, and it left the whole platform-service suite uncollectable until then. This PR restores it as a hermetic test of validate_bearer_token against a mocked cursor: accepts a valid active token; rejects missing, unknown, and inactive tokens; and fails closed when the token lookup raises. No live DB, no removed imports.

Five auth cases + one route case. I deliberately did not test validate_bearer_token's platform_key != token branch: the query is WHERE key = %s with params=(token,), so that branch is unreachable defensive code and a test for it would be manufacturing coverage.

Sonar

The initial push failed the quality gate with three issues, all in the new test files:

  • S1763 (bug, Reliability C) — valid. A yield after raise in the DB-error mock was genuinely unreachable; the @contextmanager was unnecessary since safe_cursor(...) is invoked inside the function's own try. Fixed properly rather than suppressed.
  • S4502 ×2 (vulnerability, Security D) — false positive. It flags Flask(__name__) as "CSRF protection disabled". The app is constructed in a unit test, never served, and exists only so url_map materialises; platform-service authenticates with bearer tokens and serves no cookie- or form-backed routes, so CSRF does not apply. Suppressed with # NOSONAR plus justification, matching the existing convention in backend/utils/tests/test_cors_origin.py.

Quality gate now passes with 0 open issues.

Checklist

I have read and understood the Contribution Guidelines.

🤖 Generated with Claude Code

https://claude.ai/code/session_014gUmgQQ7xV73qn6kcB7mXG

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • Bug Fixes

    • Improved authentication middleware compatibility with Flask route registration.
    • Ensured platform routes retain their correct endpoint names and register reliably.
  • Tests

    • Added coverage for valid, missing, unknown, inactive, and failed token validation scenarios.
    • Added route-registration tests to verify endpoints are exposed under their expected names.
    • Added default test environment settings for consistent test execution.

Walkthrough

The platform authentication middleware now preserves wrapped function metadata, allowing Flask routes to register under their function names without explicit endpoint arguments. Test defaults and pytest coverage were added for token validation, database errors, and route endpoint registration.

Changes

Platform routing and authentication

Layer / File(s) Summary
Preserve Flask endpoint identities
platform-service/src/unstract/platform_service/controller/platform.py
Adds functools.wraps to the authentication middleware and removes explicit endpoint arguments from platform routes.
Validate middleware and route behavior
platform-service/tests/*
Adds test environment defaults and tests for bearer-token validation, database failures, and blueprint endpoint names.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change to preserve auth middleware view identity.
Description check ✅ Passed The PR description follows the template and covers the required sections with sufficient detail.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/platform-service-auth-middleware-wraps

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.

@chandrasekharan-zipstack chandrasekharan-zipstack changed the title fix: preserve view function identity in platform-service auth middleware [MISC] Preserve view function identity in platform-service auth middleware Jul 10, 2026
@chandrasekharan-zipstack chandrasekharan-zipstack force-pushed the fix/platform-service-auth-middleware-wraps branch from fd7cf75 to 3427d73 Compare July 10, 2026 05:46
`authentication_middleware` returned an unwrapped closure, so every
decorated view function's `__name__` was "wrapper". Flask derives an
endpoint name from `__name__`, which meant:

- Six routes carried a redundant `endpoint="..."` kwarg purely to dodge
  the resulting name collision.
- `/usage`, the one authenticated route without that kwarg, registered as
  endpoint `platform.wrapper`.
- Adding any new authenticated route without `endpoint=` would fail at
  import with "View function mapping is overwriting an existing endpoint
  function: platform.wrapper".

Add `functools.wraps` and drop the now-redundant `endpoint=` kwargs. URL
rules and methods are unchanged; only the internal Flask endpoint names
change (`platform.wrapper` -> `platform.usage`). Nothing in the repo
resolves routes via `url_for`, and callers reach platform-service over
HTTP by path, so this is not externally observable.

x2text-service's equivalent middleware already patched `wrapper.__name__`
by hand; this brings platform-service in line.

Also repair `tests/test_auth_middleware.py`, which imported
`unstract.platform_service.main` (removed) and `get_account_from_bearer_token`
(removed) and so broke collection of the entire platform-service suite. It
now covers `validate_bearer_token` against a mocked cursor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014gUmgQQ7xV73qn6kcB7mXG
@chandrasekharan-zipstack chandrasekharan-zipstack force-pushed the fix/platform-service-auth-middleware-wraps branch from 3427d73 to 8cdc7e5 Compare July 10, 2026 05:51
@chandrasekharan-zipstack chandrasekharan-zipstack marked this pull request as ready for review July 10, 2026 05:52
@sonarqubecloud

Copy link
Copy Markdown

@greptile-apps

greptile-apps Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR preserves Flask view metadata in the platform-service auth middleware. The main changes are:

  • Adds functools.wraps to the authentication wrapper.
  • Removes redundant explicit endpoint names from matching route decorators.
  • Adds tests for endpoint registration and bearer-token validation.
  • Adds test-time defaults for required import-time environment settings.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.
  • The removed endpoint values match the route function names.
  • The wrapper now preserves the metadata Flask uses for default endpoint naming.

Important Files Changed

Filename Overview
platform-service/src/unstract/platform_service/controller/platform.py Adds functools.wraps to the auth wrapper and removes endpoint overrides that matched the function names.
platform-service/tests/conftest.py Sets test-scope defaults for environment variables required during platform-service imports.
platform-service/tests/test_auth_middleware.py Adds hermetic tests for valid, missing, unknown, inactive, and DB-error bearer-token cases.
platform-service/tests/test_route_endpoints.py Adds a regression test that ensures authenticated routes do not register as platform.wrapper.

Reviews (1): Last reviewed commit: "fix: preserve view function identity in ..." | Re-trigger Greptile

@coderabbitai coderabbitai Bot left a comment

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.

🧹 Nitpick comments (1)
platform-service/tests/test_route_endpoints.py (1)

11-18: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider asserting the other six endpoint names to prevent silent regressions.

The test correctly verifies the core fix (platform.wrapper absent, platform.usage present), but the six routes that had explicit endpoint= arguments removed are not checked. Adding assertions for those endpoints would catch accidental function renames that silently change endpoint names and break url_for calls.

♻️ Proposed additional assertions
 def test_routes_register_under_their_function_names() -> None:
     # NOSONAR — app is never served; bearer-token routes carry no CSRF surface.
     app = Flask(__name__)  # NOSONAR
     app.register_blueprint(platform_bp)

     endpoints = {rule.endpoint for rule in app.url_map.iter_rules()}
     assert "platform.wrapper" not in endpoints
     assert "platform.usage" in endpoints
+    assert "platform.page_usage" in endpoints
+    assert "platform.platform_details" in endpoints
+    assert "platform.cache" in endpoints
+    assert "platform.adapter_instance" in endpoints
+    assert "platform.custom_tool_instance" in endpoints
+    assert "platform.agentic_tool_instance" in endpoints
+    assert "platform.llm_profile_instance" in endpoints
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@platform-service/tests/test_route_endpoints.py` around lines 11 - 18, Extend
test_routes_register_under_their_function_names to assert that all six remaining
route endpoint names are present in the endpoints set, alongside the existing
platform.wrapper and platform.usage checks. Use the route view function names
from the blueprint as the expected endpoint strings so accidental renames are
detected.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@platform-service/tests/test_route_endpoints.py`:
- Around line 11-18: Extend test_routes_register_under_their_function_names to
assert that all six remaining route endpoint names are present in the endpoints
set, alongside the existing platform.wrapper and platform.usage checks. Use the
route view function names from the blueprint as the expected endpoint strings so
accidental renames are detected.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 78b14d02-71c6-4fe4-950e-7be3de7b1e29

📥 Commits

Reviewing files that changed from the base of the PR and between c525d1a and 8cdc7e5.

📒 Files selected for processing (4)
  • platform-service/src/unstract/platform_service/controller/platform.py
  • platform-service/tests/conftest.py
  • platform-service/tests/test_auth_middleware.py
  • platform-service/tests/test_route_endpoints.py

@github-actions

Copy link
Copy Markdown
Contributor

Unstract test results

Per-group results

Status Group Tier Passed Failed Errors Skipped Duration (s)
e2e-login e2e 2 0 0 0 1.4
e2e-smoke e2e 2 0 0 0 0.9
integration-backend integration 56 0 0 27 47.3
integration-connectors integration 1 0 0 7 8.2
unit-backend unit 118 0 0 0 21.9
unit-connectors unit 63 0 0 0 10.4
unit-core unit 27 0 0 0 1.3
unit-platform-service unit 15 0 0 0 2.7
unit-rig unit 69 0 0 0 3.7
unit-sdk1 unit 381 0 0 0 23.7
unit-workers unit 0 0 0 0 29.4
TOTAL 734 0 0 34 150.9

Critical paths

⚠️ Critical paths not yet covered

  • workflow-create-execute — Create a workflow, configure source+destination, execute, poll, fetch result. (entry: POST /api/v1/workflow/{id}/execute/; declared coverage: e2e-workflow)
  • api-deployment-run — Deploy a workflow as an API, POST a document, receive structured JSON. (entry: POST /deployment/api/{org}/{name}/; declared coverage: e2e-api-deployment)
  • prompt-studio-fetch-response — Prompt Studio: create project, add prompt, run single-pass, get response. (entry: POST /api/v1/prompt-studio/prompt-studio-tool/{id}/fetch_response/; declared coverage: e2e-prompt-studio)
  • pipeline-etl-execute — Run an ETL pipeline from source connector to destination. (entry: POST /api/v1/pipeline/{id}/execute/; declared coverage: no groups declared)
  • usage-token-tracking — Per-execution token usage is recorded and retrievable. (entry: GET /api/v1/usage/get_token_usage/; declared coverage: no groups declared)
  • workflow-execution-fan-out — Multi-file workflow execution fans out to file-processing workers and rejoins. (entry: internal: backend → rabbitmq → workers/file_processing; declared coverage: no groups declared)
  • callback-result-delivery — Async results are posted back via the callback worker. (entry: internal: workers/callback → backend /internal endpoints; declared coverage: no groups declared)
✅ Covered critical paths
  • auth-login — covered by e2e-login
  • adapter-register-llm — covered by integration-backend

@chandrasekharan-zipstack

Copy link
Copy Markdown
Contributor Author

CodeRabbit nitpick: Consider asserting the other six endpoint names to prevent silent regressions… would catch accidental function renames that silently change endpoint names and break url_for calls.

Declining this one, deliberately.

The stated risk doesn't exist in this repo: there are zero url_for, view_functions, or url_map call sites anywhere (venvs excluded). Nothing resolves a platform-service route by endpoint name — the backend, workers, and unstract/sdk1/src/unstract/sdk1/audit.py:128 all reach it over HTTP by path. That's precisely why renaming platform.wrapperplatform.usage is behaviour-preserving in the first place; if any url_for existed, this PR would be a breaking change rather than a cleanup.

So pinning the other six endpoint names would assert on an internal Flask registry key that no code reads, and would need editing every time a route is added. The one assertion that carries signal is "platform.wrapper" not in endpoints — it fails loudly (View function mapping is overwriting an existing endpoint function: platform.wrapper) the moment functools.wraps is removed, which is the regression this PR guards against. Verified by re-running the test with the decorator stripped.

Happy to add them if a maintainer wants the endpoint names pinned as a contract, but today they aren't one.

@chandrasekharan-zipstack chandrasekharan-zipstack merged commit 09b6834 into main Jul 10, 2026
15 checks passed
@chandrasekharan-zipstack chandrasekharan-zipstack deleted the fix/platform-service-auth-middleware-wraps branch July 10, 2026 06:29
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.

3 participants