Skip to content

fix(middleware): devolve a HTTPException que o runtime_context levanta (CRM-572) - #70

Merged
gomessguii merged 1 commit into
developfrom
fix/CRM-572-tenant-forbidden-403
Sep 9, 2026
Merged

gomessguii merged 1 commit into
developfrom
fix/CRM-572-tenant-forbidden-403

Conversation

@gomessguii

@gomessguii gomessguii commented Sep 9, 2026 •

Copy link
Copy Markdown
Member

O que

Achado M2 do review do CRM-572. O TenantContextMiddleware tratava qualquer exceção do current_context_id como falha de resolução e seguia com a request sem contexto. Uma recusa que o extension point decidiu (uma HTTPException 403) então aparecia mais adiante como 500 no banco ou como leitura vazia — nunca como 403. Agora uma HTTPException vinda do runtime_context vira resposta com o mesmo formato de erro do EvoAuthMiddleware; qualquer outra exceção continua fail-open como antes.

A classe saiu do main.py para src/middleware/tenant_context.py (sem mudança de corpo além do novo except) para ser testável sem importar o app, que faz create_all no import.

Prova

  • tests/unit/test_tenant_context_middleware.py: 403 devolvido tal qual e sem bind_context; exceção comum segue fail-open; contexto resolvido é vinculado.
  • Mutação: removendo o except HTTPException, o teste do 403 fica vermelho (1 failed, 2 passed).
  • tests/unit na imagem evo-ai-processor:dev: 446 passed; os 8 erros em test_perplexity_responses_roundtrip.py são baseline da develop (import do litellm na imagem).

Summary by Sourcery

Propagate runtime-context HTTP refusals directly to clients while retaining fail-open tenant-context resolution.

Bug Fixes:

  • Return HTTPException responses raised during runtime context resolution with the expected authentication error format instead of allowing them to surface as downstream failures.

Enhancements:

  • Move TenantContextMiddleware into its own module while preserving fail-open behavior for ordinary context-resolution and binding failures.
  • Keep resolved tenant contexts bound around downstream requests and avoid binding refused requests.

Tests:

  • Add unit coverage for HTTP refusals, ordinary resolution failures, resolved context binding, and non-standard HTTP status codes.

@sourcery-ai

sourcery-ai Bot commented Sep 9, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

O PR extrai o TenantContextMiddleware para um módulo testável e passa a transformar HTTPException levantada pelo runtime_context em resposta JSON HTTP correspondente, mantendo o comportamento fail-open para outras falhas e cobrindo os fluxos principais com testes unitários.

Sequence diagram for tenant context resolution and HTTPException handling

sequenceDiagram
    participant Client
    participant EvoAuthMiddleware
    participant TenantContextMiddleware
    participant RuntimeContext
    participant App

    Client->>EvoAuthMiddleware: HTTP request
    EvoAuthMiddleware->>TenantContextMiddleware: Forward with user_context
    TenantContextMiddleware->>RuntimeContext: current_context_id(request)
    alt HTTPException raised
        RuntimeContext-->>TenantContextMiddleware: HTTPException
        TenantContextMiddleware->>TenantContextMiddleware: _error_response(exc)
        TenantContextMiddleware-->>Client: JSON error response
    else Context resolved
        RuntimeContext-->>TenantContextMiddleware: context_id
        TenantContextMiddleware->>RuntimeContext: bind_context(context_id)
        TenantContextMiddleware->>App: Forward request
        App-->>Client: Application response
    else Other resolution exception
        RuntimeContext-->>TenantContextMiddleware: Exception
        TenantContextMiddleware->>App: Forward request without context
        App-->>Client: Application response
    end
Loading

File-Level Changes

Change Details Files
Propagar recusas HTTPException do runtime_context como respostas HTTP no formato padrão de autenticação, sem vincular contexto.
  • Captura HTTPException separadamente antes do fallback genérico.
  • Gera resposta JSON com status, código ERR_* e mensagem da exceção.
  • Mantém o comportamento fail-open para exceções comuns e o binding para contextos resolvidos.
src/middleware/tenant_context.py
Extrair o middleware de contexto de main.py para um módulo independente e testável.
  • Remove a implementação local e seus imports de main.py.
  • Preserva o middleware como ASGI puro e a ordem relativa ao EvoAuthMiddleware.
src/main.py
src/middleware/tenant_context.py
Adicionar cobertura unitária para os caminhos de recusa, falha de resolução e binding.
  • Verifica resposta 403 e ausência de bind para HTTPException.
  • Verifica fallback sem contexto para erro comum.
  • Verifica binding do contexto resolvido e isolamento do runtime_context entre testes.
tests/unit/test_tenant_context_middleware.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

@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="src/middleware/tenant_context.py" line_range="88" />
<code_context>
+
+    @staticmethod
+    def _error_response(exc: HTTPException) -> JSONResponse:
+        phrase = HTTPStatus(exc.status_code).phrase
+        return JSONResponse(
+            status_code=exc.status_code,
</code_context>
<issue_to_address>
**issue (bug_risk):** HTTPStatus(exc.status_code) raises ValueError for valid custom HTTP status codes such as 499, so an HTTPException raised by the runtime_context extension point is converted into a 500 instead of being returned to the client.

**Triggers:** When the extension point raises an HTTPException with a non-standard status code, such as the project's supported 499 Client Closed Request code.

**Suggested fix:** Use a phrase lookup with a fallback (for example, `HTTPStatus._value2member_map_.get(exc.status_code)` and a generic phrase) instead of constructing HTTPStatus unconditionally.

```suggestion
        phrase = HTTPStatus._value2member_map_.get(exc.status_code, HTTPStatus.INTERNAL_SERVER_ERROR).phrase
```
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and this changes tenant authorization behavior by turning an HTTPException from the runtime context extension into an immediate 4xx response instead of continuing unbound. If the classification or response handling is wrong, legitimate requests could be denied or a tenant refusal could be mishandled; reverting restores the previous behavior, but any incorrect access decision may already have affected requests.

Blocking findings: src/middleware/tenant_context.py:88


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

Comment thread src/middleware/tenant_context.py Outdated
@gomessguii
gomessguii force-pushed the fix/CRM-572-tenant-forbidden-403 branch from 7b8ceec to 69405e4 Compare September 9, 2026 20:16
…(CRM-572)

TenantContextMiddleware treated every exception from current_context_id as a
resolution failure and ran the request unbound, so a refusal the extension
point decided on (a 403) surfaced downstream as a 500 or as an empty read.
The class moves to src/middleware/tenant_context.py so it can be tested
without importing the app.
@gomessguii
gomessguii force-pushed the fix/CRM-572-tenant-forbidden-403 branch from 69405e4 to 935de2d Compare September 9, 2026 20:18

@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.

Sourcery assessment

Approved.

@gomessguii
gomessguii merged commit b33da1d into develop Sep 9, 2026
5 checks passed
@gomessguii
gomessguii deleted the fix/CRM-572-tenant-forbidden-403 branch September 9, 2026 20:21
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.

1 participant