Skip to content

fix(chore): centralize Layer-1 visibility filter across REST endpoints#4654

Open
bogdanmariusc10 wants to merge 1 commit intomainfrom
4451-chore-centralize-layer-1-visibility-filter-across-mainpy-rest-endpoints
Open

fix(chore): centralize Layer-1 visibility filter across REST endpoints#4654
bogdanmariusc10 wants to merge 1 commit intomainfrom
4451-chore-centralize-layer-1-visibility-filter-across-mainpy-rest-endpoints

Conversation

@bogdanmariusc10
Copy link
Copy Markdown
Collaborator

🔗 Related Issue

Closes #4451


📝 Summary

Centralized Layer-1 visibility filtering across mcpgateway/main.py REST endpoints by replacing 22 inline derivations with calls to get_scoped_resource_access_context(request, user).

Problem: The codebase contained 22 near-identical copies of the Layer-1 admin-bypass + public-only-secure-default derivation pattern. These copies drifted independently (different comments, missing assignments, mixed audit-context patterns), creating maintenance burden and regression risk.

Solution: Replace inline derivations with centralized helper from mcpgateway/auth_context.py, preserving 4 exceptions that require custom handling for federation, audit capture, or custom return contracts.

Bug Fix: The centralized helper correctly enables Layer-1 admin bypass for basic-auth/dev-mode admins (non-JWT contexts). The inline pattern missed this case because it only checked is_admin from the JWT payload. This is a latent bug-fix documented in regression tests.


🏷️ Type of Change

  • Bug fix
  • Feature / Enhancement
  • Documentation
  • Refactor
  • Chore (deps, CI, tooling)
  • Other (describe below)

🧪 Verification

Check Command Status
Lint suite make lint ✅ Pass
Unit tests make test ✅ Pass
Coverage ≥ 80% make coverage ✅ Pass

✅ Checklist

  • Code formatted (make black isort pre-commit)
  • Tests added/updated for changes
  • Documentation updated (if applicable)
  • No secrets or credentials committed

📓 Notes

Changes Made

1. mcpgateway/main.py (22 refactorings)

Replaced inline pattern:

user_email, token_teams, is_admin = get_rpc_filter_context(request, user)
if is_admin and token_teams is None:
    user_email = None
    token_teams = None  # Admin unrestricted
elif token_teams is None:
    token_teams = []  # Non-admin without teams = public-only

With centralized helper:

user_email, token_teams = get_scoped_resource_access_context(request, user)

Endpoints refactored:

  • REST: handle_completion, get_server, update_server, sse_endpoint, server_get_tools, server_get_resources, server_get_prompts, list_a2a_agents, list_tools, list_resources, list_resource_templates, list_prompts, read_resource, get_resource, subscribe_resource, get_prompt_no_args, get_prompt, get_prompt_with_args, get_gateway, update_gateway, delete_gateway
  • JSON-RPC: tools/list, gateways/list, resources/list, resources/subscribe, prompts/list, prompts/get, completion/complete

2. Preserved 4 Exceptions

These endpoints retain the 3-tuple pattern for specific reasons:

Line Endpoint Reason
4434 get_a2a_agent Preserves user_email for federation visibility
4735 invoke_a2a_agent Preserves user_email for federation user_id construction
8704 _get_internal_a2a_scope_context Custom return contract (user_email, teams)
9933 JSON-RPC tools/list Captures _req_email, _req_is_admin for audit before applying rule

3. Test Updates (13 methods)

Updated mocks from 3-tuple to 2-tuple pattern:

test_main.py (9 methods):

  • Completion endpoints: test_handle_completion_endpoint, test_handle_completion_endpoint_admin_bypass, test_handle_completion_endpoint_defaults_to_public_scope_when_token_teams_none
  • Tag endpoints: test_list_tags_passes_token_scope, test_get_entities_by_tag_passes_public_only_scope, test_list_tags_admin_bypass_passes_unrestricted_scope, test_list_tags_defaults_to_public_scope_when_token_teams_none, test_get_entities_by_tag_admin_bypass_passes_unrestricted_scope, test_get_entities_by_tag_defaults_to_public_scope_when_token_teams_none
  • RPC completion: test_rpc_completion_complete, test_rpc_completion_complete_admin_bypass, test_rpc_completion_complete_defaults_to_public_scope_when_token_teams_none

test_main_extended.py (4 methods):

  • test_server_get_tools_admin_bypass, test_server_get_resources_public_scope, test_server_get_prompts_public_scope, test_list_tools_admin_bypass_and_public_only_default

4. Regression Tests Added (2 new tests)

Added comprehensive tests documenting the basic-auth admin behavior change:

  • test_get_scoped_resource_access_context_basic_auth_admin_bypass: Verifies basic-auth admins get (None, None) for unrestricted access (bug-fix)
  • test_get_scoped_resource_access_context_basic_auth_non_admin_public_only: Verifies non-admin basic-auth users maintain secure default (email, [])

Impact

Code Quality:

  • Removed ~126 lines of repetitive inline logic
  • Reduced maintenance surface from 22 locations to 1 centralized function
  • Eliminated drift risk between endpoint implementations

Bug Fix:

  • Basic-auth/dev-mode admins now correctly receive admin bypass (None, None)
  • Previously got public-only scope (email, []) due to inline pattern only checking JWT is_admin

Consistency:

Design Decisions

  1. Preserved 4 exceptions: These endpoints have legitimate reasons to use the 3-tuple pattern (federation, audit, custom contracts). Documented inline with comments.

  2. Regression tests over inline comments: Added comprehensive tests documenting the basic-auth admin behavior change rather than just inline comments, ensuring the fix is verified.

  3. Minimal scope: Only refactored main.py REST endpoints. Excluded streamablehttp_transport.py (companion issue) and routers/teams.py (already migrated).

Related Work

Replace 22 inline Layer-1 derivations with centralized
get_scoped_resource_access_context() calls for consistency and
maintainability.

Changes:
- Refactor 18 endpoints to use centralized helper
- Preserve 4 exceptions (A2A federation, custom contracts, audit capture)
- Update 13 test mocks from 3-tuple to 2-tuple pattern
- Add regression tests for basic-auth admin behavior

Bug fix: Basic-auth/dev-mode admins now correctly receive admin bypass
(None, None) instead of public-only scope. The inline pattern missed
non-JWT admin contexts; the centralized helper handles them correctly.

Closes #4451

Signed-off-by: Bogdan-Marius-Catanus <bogdan-marius.catanus@ibm.com>
@bogdanmariusc10 bogdanmariusc10 added chore Linting, formatting, dependency hygiene, or project maintenance chores api REST API Related item labels May 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api REST API Related item chore Linting, formatting, dependency hygiene, or project maintenance chores

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[CHORE]: Centralize Layer-1 visibility filter across main.py REST endpoints

1 participant