Skip to content

[TESTING][SECURITY]: RBAC manual test plan (visibility, teams, token scope) #2388

@crivetimihai

Description

@crivetimihai

🔒 Epic: RBAC Manual Test Plan (Visibility, Teams, Token Scope)

Goal

Produce a comprehensive manual RBAC test plan that documents the full access-control flow across resources, teams, and token types. The plan serves as the canonical reference for expected behavior and provides the baseline for automated regression coverage.

Why Now?

RBAC behavior spans middleware, service-layer filtering, and token scoping. A manual plan is needed to:

  1. End-to-End Validation: Validate the complete flow from authentication → token parsing → RBAC → visibility filtering in real environments
  2. Human-Readable Specification: Provide a canonical reference for behavior, troubleshooting, and onboarding
  3. Release Gate: Enable consistent validation of Release 1.0.0-RC1 candidates
  4. Regression Baseline: Serve as the specification for automated test coverage

📖 User Stories

US-1: QA Lead - Complete End-to-End Manual Coverage

As a QA Lead
I want a comprehensive manual test plan covering all RBAC flows
So that release candidates can be validated quickly and consistently

Acceptance Criteria:

Feature: RBAC Manual Test Plan Coverage

  Scenario: Test plan provides complete resource coverage
    Given the manual test plan document
    When I review the coverage matrix
    Then I should see test cases for all resource types:
      | Resource Type |
      | Tools         |
      | Resources     |
      | Prompts       |
      | Servers       |
      | Gateways      |
      | A2A Agents    |
    And each resource type should have visibility tests (private, team, public)
    And each test case should have clear preconditions
    And each test case should have step-by-step procedures
    And each test case should have expected results

  Scenario: Test plan is reproducible by any tester
    Given a tester with database access
    When the tester follows the setup instructions
    Then the preconditions should be established within 15 minutes
    And each test case should be executable independently
    And results should be deterministic and repeatable

Technical Requirements:

  • Clear preconditions and environment setup steps
  • Step-by-step procedures with curl/API examples
  • Expected results with HTTP status codes and response bodies
  • Coverage across tools/resources/prompts/servers/gateways/A2A agents
  • Rollback/cleanup procedures for each test
US-2: Security Engineer - Token Types & Scope Behavior

As a Security Engineer
I want explicit manual tests for admin, team-scoped, and public-only tokens
So that privilege boundaries are validated before each release

Acceptance Criteria:

Feature: Token Type Access Control

  Scenario Outline: Admin token grants unrestricted access
    Given an admin token with is_admin=true and teams=null
    And a <visibility> resource owned by another user
    When I access the resource with the admin token
    Then the request should return status <status>
    And the response should contain the resource data

    Examples:
      | visibility | status |
      | private    | 200    |
      | team       | 200    |
      | public     | 200    |

  Scenario: Team-scoped token restricts to team resources
    Given a team-scoped token with teams=["team-alpha"]
    And a team-visible resource in "team-alpha"
    And a team-visible resource in "team-beta"
    When I list resources with the team-scoped token
    Then I should see the "team-alpha" resource
    And I should NOT see the "team-beta" resource

  Scenario: Public-only token cannot access private resources
    Given a public-only token with teams=[]
    And a private resource owned by the token's user
    When I access the private resource
    Then the request should return status 403
    And the response should contain "Access denied"

  Scenario: Public-only token can access public resources
    Given a public-only token with teams=[]
    And a public resource in any team
    When I access the public resource
    Then the request should return status 200

Technical Requirements:

  • Admin token bypass confirmed for all visibility levels
  • Team-scoped token restricted to specified teams only
  • Public-only token cannot access private resources (even if owner)
  • Token claims verified: is_admin, teams, sub (user email)
  • Error messages do not leak internal details
US-3: MCP Operator - Permissive Mode Exception

As an MCP Operator
I want manual tests for permissive vs strict MCP authentication modes
So that public-only unauthenticated access is validated correctly

Acceptance Criteria:

Feature: MCP Authentication Modes

  Scenario: Permissive mode allows unauthenticated public access
    Given MCP_REQUIRE_AUTH=false (permissive mode)
    And a public tool exists
    And a team-visible tool exists
    When I invoke tools/list without authentication
    Then I should receive the public tool
    And I should NOT receive the team-visible tool
    And the response should NOT contain private tools

  Scenario: Permissive mode blocks unauthenticated team access
    Given MCP_REQUIRE_AUTH=false (permissive mode)
    And a team-visible resource exists
    When I attempt to access the team resource without authentication
    Then the request should return status 403
    And the response should indicate "Authentication required for team resources"

  Scenario: Strict mode blocks all unauthenticated access
    Given MCP_REQUIRE_AUTH=true (strict mode)
    And a public tool exists
    When I invoke tools/list without authentication
    Then the request should return status 401
    And the response should indicate "Authentication required"

  Scenario: Authenticated user in permissive mode gets full RBAC
    Given MCP_REQUIRE_AUTH=false (permissive mode)
    And a valid JWT token for a team member
    When I invoke tools/list with the token
    Then I should receive public tools
    And I should receive team-visible tools for my teams
    And I should receive my private tools

Technical Requirements:

  • Permissive mode: unauthenticated → public-only (no owner/team access)
  • Strict mode: unauthenticated → 401 for all endpoints
  • Authenticated requests follow full RBAC regardless of mode
  • Configuration toggle: MCP_REQUIRE_AUTH environment variable
  • Test both SSE and WebSocket transports
US-4: Developer - Visibility and Ownership Semantics

As a Developer
I want tests that validate visibility vs ownership distinctions
So that I understand the correct access behavior

Acceptance Criteria:

Feature: Visibility vs Ownership

  Scenario: Private visibility restricts to owner only
    Given User A owns a private resource in Team T
    And User B is a member of Team T
    When User B attempts to access the private resource
    Then the request should return status 403
    And only User A should be able to access the resource

  Scenario: Team visibility allows all team members
    Given User A owns a team-visible resource in Team T
    And User B is a member of Team T
    And User C is NOT a member of Team T
    When User B accesses the resource
    Then the request should return status 200
    When User C attempts to access the resource
    Then the request should return status 403

  Scenario: Public visibility allows all authenticated users
    Given User A owns a public resource in Team T
    And User D is authenticated but not in Team T
    When User D accesses the resource
    Then the request should return status 200
    And the response should contain the resource data

  Scenario: Resource belongs to team but visibility controls access
    Given a public resource belongs to Team T (team_id=T)
    And User B is NOT a member of Team T
    When User B accesses the resource
    Then the request should return status 200
    And the resource's team_id should still show Team T

Technical Requirements:

  • owner_email → who created the resource
  • team_id → organizational ownership (where it belongs)
  • visibility → who can access (private/team/public)
  • Private: owner only (even team members excluded)
  • Team: all team members regardless of who owns it
  • Public: all authenticated users
US-5: Platform Admin - Team Filter Semantics

As a Platform Administrator
I want tests that validate team filtering behavior on list endpoints
So that users see only resources belonging to the filtered team

Acceptance Criteria:

Feature: Team Filter Behavior

  Scenario: Team filter shows only resources belonging to that team
    Given User A is a member of Team T and Team X
    And Team T has: Tool-T1 (team), Tool-T2 (public)
    And Team X has: Tool-X1 (public)
    When User A lists tools with team_id=T
    Then the response should contain Tool-T1 and Tool-T2
    And the response should NOT contain Tool-X1
    And public resources from other teams are excluded

  Scenario: Team filter respects visibility within the team
    Given User A is a member of Team T
    And Team T has:
      | Resource    | Visibility | Owner  |
      | Private-A   | private    | User A |
      | Private-B   | private    | User B |
      | Team-Tool   | team       | User B |
      | Public-Tool | public     | User B |
    When User A lists tools with team_id=T
    Then the response should contain: Private-A, Team-Tool, Public-Tool
    And the response should NOT contain: Private-B

  Scenario: Non-member cannot filter by team they don't belong to
    Given User C is NOT a member of Team T
    When User C lists resources with team_id=T
    Then the response should return an empty list
    And no resources from Team T should be visible

Technical Requirements:

  • ?team_id=X filter restricts to resources with team_id=X
  • Public resources from OTHER teams are excluded when filtering
  • Visibility rules still apply within the filtered team
  • Non-members get empty results (not an error)
US-6: Security Auditor - Token Scope Intersection

As a Security Auditor
I want tests that verify token scopes intersect with RBAC permissions
So that tokens cannot grant more access than RBAC allows

Acceptance Criteria:

Feature: Token Scope Intersection with RBAC

  Scenario: Token permissions restrict RBAC grants
    Given RBAC grants User A tools.execute permission
    And User A's token has permissions=["tools.read"]
    When User A attempts to execute a tool
    Then the request should return status 403
    And the denial should cite "Token scope insufficient"

  Scenario: RBAC denial overrides token grants
    Given RBAC denies User B prompts.delete permission
    And User B's token has permissions=["prompts.*"]
    When User B attempts to delete a prompt
    Then the request should return status 403
    And the denial should cite "Permission denied"

  Scenario: Token without explicit permissions inherits RBAC
    Given RBAC grants User A resources.read permission
    And User A's token has permissions=null (inherit)
    When User A reads a resource
    Then the request should return status 200

  Scenario: Token server_id scope limits access
    Given a token scoped to server_id="server-1"
    And Tool A belongs to server-1
    And Tool B belongs to server-2
    When the token holder lists tools
    Then the response should contain Tool A
    And the response should NOT contain Tool B

Technical Requirements:

  • Token permissions field intersects with RBAC-granted permissions
  • Token cannot expand access beyond what RBAC grants
  • RBAC denial always wins regardless of token claims
  • server_id scope restricts to specific server's resources
  • Future: IP restrictions, time windows, usage limits
US-7: Release Manager - Ownership CRUD Validation

As a Release Manager
I want tests that validate ownership rules for create/update/delete
So that users can only modify resources they're authorized to change

Acceptance Criteria:

Feature: Ownership CRUD Rules

  Scenario: Owner can update their private resource
    Given User A owns a private tool
    When User A updates the tool description
    Then the request should return status 200
    And the tool should reflect the updated description

  Scenario: Owner can delete their private resource
    Given User A owns a private prompt
    When User A deletes the prompt
    Then the request should return status 200
    And the prompt should no longer exist

  Scenario: Non-owner cannot update private resource
    Given User A owns a private resource in Team T
    And User B is a member of Team T (not owner)
    When User B attempts to update the resource
    Then the request should return status 403

  Scenario: Team owner can update team-visible resources
    Given User A is an owner of Team T
    And User B created a team-visible tool in Team T
    When User A updates the tool
    Then the request should return status 200
    And team owners have management rights over team resources

  Scenario: Team member can update their own creations
    Given User B is a member (not owner) of Team T
    And User B created a team-visible resource
    When User B updates their resource
    Then the request should return status 200

Technical Requirements:

  • Private: only owner can update/delete
  • Team (owner): full CRUD on all team resources
  • Team (member): CRUD on their own creations
  • Public: same rules as team visibility for modifications
  • Delete operations respect the same ownership rules

🏗 Test Architecture

Access Control Decision Flow

┌─────────────────────────────────────────────────────────────────────────────┐
│                        ACCESS CONTROL DECISION FLOW                          │
└─────────────────────────────────────────────────────────────────────────────┘

                              ┌──────────────┐
                              │ User Request │
                              └──────┬───────┘
                                     │
                              ┌──────▼───────┐
                              │ Token Valid? │
                              └──────┬───────┘
                                     │
                    ┌────────────────┼────────────────┐
                    │                │                │
              ┌─────▼─────┐   ┌──────▼──────┐   ┌─────▼─────┐
              │ Admin     │   │ Team-Scoped │   │ Public    │
              │ (is_admin │   │ (teams=[T]) │   │ Only      │
              │ +null)    │   │             │   │ (teams=[])│
              └─────┬─────┘   └──────┬──────┘   └─────┬─────┘
                    │                │                │
              ┌─────▼─────┐   ┌──────▼──────┐   ┌─────▼─────┐
              │ ALLOW ALL │   │ Check       │   │ ONLY      │
              │           │   │ Visibility  │   │ PUBLIC    │
              └───────────┘   └──────┬──────┘   └───────────┘
                                     │
                    ┌────────────────┼────────────────┐
                    │                │                │
              ┌─────▼─────┐   ┌──────▼──────┐   ┌─────▼─────┐
              │ public    │   │ team        │   │ private   │
              │ ALLOW     │   │ team_id in  │   │ owner ==  │
              │           │   │ token.teams?│   │ user?     │
              └───────────┘   └─────────────┘   └───────────┘

Token Scope Intersection

┌─────────────────────────────────────────────────────────────────────────────┐
│                    TOKEN SCOPE + RBAC INTERSECTION                           │
└─────────────────────────────────────────────────────────────────────────────┘

                ┌───────────────────────────┐
                │ Request: resource.action  │
                └──────────────┬────────────┘
                               │
                ┌──────────────▼────────────┐
                │ RBAC grants permission?   │
                └──────────────┬────────────┘
                               │
                    ┌──────────┴──────────┐
                    │                     │
                  NO ──► DENY          YES (continue)
                                          │
                              ┌───────────▼───────────┐
                              │ Token scope allows?   │
                              │ (permissions, server, │
                              │  IP, time, usage)     │
                              └───────────┬───────────┘
                                          │
                               ┌──────────┴──────────┐
                               │                     │
                            NO ──► DENY          YES ──► ALLOW

List Endpoint with Team Filter

┌─────────────────────────────────────────────────────────────────────────────┐
│                   LIST ENDPOINT (team_id filter)                             │
└─────────────────────────────────────────────────────────────────────────────┘

                         GET /resources?team_id=T
                                  │
                    ┌─────────────▼─────────────┐
                    │ User member of Team T?    │
                    └─────────────┬─────────────┘
                                  │
              ┌───────────────────┼───────────────────┐
              │ NO                                    │ YES
        ┌─────▼─────┐                          ┌──────▼──────┐
        │ Return [] │                          │ Filter by   │
        │ (empty)   │                          │ team_id = T │
        └───────────┘                          └──────┬──────┘
                                                      │
                          ┌───────────────────────────┼───────────────┐
                          │                           │               │
                   ┌──────▼──────┐             ┌──────▼──────┐ ┌──────▼──────┐
                   │ visibility= │             │ visibility= │ │ visibility= │
                   │ private     │             │ team        │ │ public      │
                   │ owner=user? │             │ INCLUDE     │ │ INCLUDE     │
                   └──────┬──────┘             └─────────────┘ └─────────────┘
                          │
              ┌───────────┼───────────┐
              │ YES       │           │ NO
        ┌─────▼─────┐           ┌─────▼─────┐
        │ INCLUDE   │           │ EXCLUDE   │
        └───────────┘           └───────────┘

NOTE: Resources with team_id != T are NEVER included, even if public.

📋 Test Environment Setup

Preconditions

Before executing tests, establish the following environment:

1. Users

User ID Email Is Admin Teams
User A alice@example.com No Team T (owner), Team X (member)
User B bob@example.com No Team X (owner)
User C charlie@example.com No (none)
Admin admin@example.com Yes (admin bypass)

2. Teams

Team ID Name Visibility
Team T team-alpha private
Team X team-beta public

3. Test Resources (Created by User A in Team T)

Create one resource of each type for each visibility level:

Resource Type Private Team Public
Tools tool-priv-a tool-team-a tool-pub-a
Resources res-priv-a res-team-a res-pub-a
Prompts prompt-priv-a prompt-team-a prompt-pub-a
Servers srv-priv-a srv-team-a srv-pub-a
Gateways gw-priv-a gw-team-a gw-pub-a
A2A Agents a2a-priv-a a2a-team-a a2a-pub-a

4. Token Types

Generate the following tokens for testing:

# Admin token (is_admin=true, teams=null)
export ADMIN_TOKEN=$(python -m mcpgateway.utils.create_jwt_token \
  --username admin@example.com --is-admin --exp 60 --secret $JWT_SECRET_KEY)

# Team-scoped token for User A (teams=["team-alpha"])
export TEAM_A_TOKEN=$(python -m mcpgateway.utils.create_jwt_token \
  --username alice@example.com --teams '["team-alpha"]' --exp 60 --secret $JWT_SECRET_KEY)

# Public-only token for User A (teams=[])
export PUBLIC_ONLY_TOKEN=$(python -m mcpgateway.utils.create_jwt_token \
  --username alice@example.com --teams '[]' --exp 60 --secret $JWT_SECRET_KEY)

# User B token (team-beta member)
export USER_B_TOKEN=$(python -m mcpgateway.utils.create_jwt_token \
  --username bob@example.com --teams '["team-beta"]' --exp 60 --secret $JWT_SECRET_KEY)

# User C token (no teams)
export USER_C_TOKEN=$(python -m mcpgateway.utils.create_jwt_token \
  --username charlie@example.com --teams '[]' --exp 60 --secret $JWT_SECRET_KEY)

5. Environment Configuration

# For permissive mode tests
export MCP_REQUIRE_AUTH=false

# For strict mode tests
export MCP_REQUIRE_AUTH=true

🧪 Manual Test Cases

Section 1: Visibility Access (Authenticated)

Case Auth Token Resource Visibility Owner Expected HTTP Status
V-01 User B team-scoped tool-pub-a public User A ALLOW 200
V-02 User B team-scoped tool-team-a team User A DENY 403
V-03 User B team-scoped tool-priv-a private User A DENY 403
V-04 User A team-scoped tool-priv-a private User A ALLOW 200
V-05 User A team-scoped tool-team-a team User A ALLOW 200
V-06 User C public-only tool-pub-a public User A ALLOW 200
V-07 User C public-only tool-team-a team User A DENY 403
V-08 User C public-only tool-priv-a private User A DENY 403
V-01: Access public resource as non-owner

Preconditions:

  • User B authenticated with team-scoped token (team-beta)
  • tool-pub-a exists with visibility=public in Team T

Steps:

curl -X GET "http://localhost:4444/tools/tool-pub-a" \
  -H "Authorization: Bearer $USER_B_TOKEN"

Expected Result:

  • HTTP 200 OK
  • Response body contains tool details
  • Public resources are accessible to all authenticated users
V-02: Deny team resource access to non-member

Preconditions:

  • User B authenticated with team-scoped token (team-beta)
  • tool-team-a exists with visibility=team in Team T
  • User B is NOT a member of Team T

Steps:

curl -X GET "http://localhost:4444/tools/tool-team-a" \
  -H "Authorization: Bearer $USER_B_TOKEN"

Expected Result:

  • HTTP 403 Forbidden
  • Response: {"detail": "Access denied"}
  • Team resources restricted to team members
V-03: Deny private resource access to non-owner

Preconditions:

  • User B authenticated
  • tool-priv-a exists with visibility=private, owner=User A

Steps:

curl -X GET "http://localhost:4444/tools/tool-priv-a" \
  -H "Authorization: Bearer $USER_B_TOKEN"

Expected Result:

  • HTTP 403 Forbidden
  • Private resources only accessible by owner

Section 2: Token Type Behavior

Case Token Type is_admin teams claim Access Level Expected
T-01 Admin true null All resources ALLOW ALL
T-02 Team-Scoped false ["team-alpha"] Team T + public + owned ALLOW SCOPED
T-03 Public-Only false [] Public only ALLOW PUBLIC
T-04 Public-Only false [] Private (even if owner) DENY
T-01: Admin token bypasses all visibility checks

Preconditions:

  • Admin token with is_admin=true, teams=null
  • Private resources from multiple users exist

Steps:

# Access User A's private tool
curl -X GET "http://localhost:4444/tools/tool-priv-a" \
  -H "Authorization: Bearer $ADMIN_TOKEN"

# Access User B's private resource (if exists)
curl -X GET "http://localhost:4444/resources" \
  -H "Authorization: Bearer $ADMIN_TOKEN"

Expected Result:

  • HTTP 200 OK for all requests
  • Admin can access any resource regardless of visibility
  • Response includes all resources
T-04: Public-only token denies owner's private resources

Preconditions:

  • User A's public-only token (teams=[])
  • User A owns tool-priv-a (private)

Steps:

curl -X GET "http://localhost:4444/tools/tool-priv-a" \
  -H "Authorization: Bearer $PUBLIC_ONLY_TOKEN"

Expected Result:

  • HTTP 403 Forbidden
  • Public-only tokens cannot access private resources
  • Even ownership doesn't override teams=[] restriction

Section 3: Team Filter Semantics

Case User Filter Resource Team Visibility Expected
F-01 User A team_id=T Team T team LIST
F-02 User A team_id=T Team T public LIST
F-03 User A team_id=T Team T private (owner) LIST
F-04 User A team_id=T Team T private (not owner) DROP
F-05 User A team_id=T Team X public DROP
F-06 User B team_id=T Team T public DROP (not member)
F-05: Public resources from other teams excluded when filtering

Preconditions:

  • User A is member of Team T and Team X
  • tool-pub-x exists with visibility=public in Team X
  • User A lists with team_id=T filter

Steps:

curl -X GET "http://localhost:4444/tools?team_id=team-alpha" \
  -H "Authorization: Bearer $TEAM_A_TOKEN"

Expected Result:

  • Response contains only Team T resources
  • tool-pub-x is NOT included (belongs to Team X)
  • Team filter restricts to resources BELONGING to that team

Section 4: Token Scope Intersection

Case RBAC Grants Token Permissions Expected
S-01 tools.execute null (inherit) ALLOW
S-02 tools.execute ["tools.read"] DENY (scope restricts)
S-03 (no grant) ["tools.execute"] DENY (RBAC wins)
S-04 resources.read ["resources.*"] ALLOW
S-02: Token scope restricts RBAC-granted permission

Preconditions:

  • User A has RBAC permission tools.execute
  • Token created with permissions=["tools.read"]
  • A tool exists that User A owns

Steps:

# Create token with restricted scope
RESTRICTED_TOKEN=$(python -m mcpgateway.utils.create_jwt_token \
  --username alice@example.com \
  --teams '["team-alpha"]' \
  --permissions '["tools.read"]' \
  --exp 60 --secret $JWT_SECRET_KEY)

# Attempt to execute tool
curl -X POST "http://localhost:4444/tools/tool-pub-a/invoke" \
  -H "Authorization: Bearer $RESTRICTED_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"arguments": {}}'

Expected Result:

  • HTTP 403 Forbidden
  • Response: {"detail": "Token scope insufficient"}
  • Token permissions=["tools.read"] doesn't include tools.execute

Section 5: MCP Permissive Mode

Case MCP_REQUIRE_AUTH Auth Resource Visibility Expected
M-01 false none public ALLOW
M-02 false none team DENY
M-03 false none private DENY
M-04 true none public DENY (401)
M-05 false valid team (member) ALLOW
M-01: Unauthenticated access to public tools in permissive mode

Preconditions:

  • MCP_REQUIRE_AUTH=false
  • Server restarted with permissive mode
  • tool-pub-a exists with visibility=public

Steps:

# MCP tools/list without auth
curl -X POST "http://localhost:4444/mcp/sse" \
  -H "Content-Type: application/json" \
  -d '{"method": "tools/list", "params": {}}'

Expected Result:

  • Response includes tool-pub-a
  • Response does NOT include team/private tools
  • Unauthenticated = public-only access
M-04: Strict mode rejects unauthenticated requests

Preconditions:

  • MCP_REQUIRE_AUTH=true
  • Server restarted with strict mode

Steps:

curl -X POST "http://localhost:4444/mcp/sse" \
  -H "Content-Type: application/json" \
  -d '{"method": "tools/list", "params": {}}'

Expected Result:

  • HTTP 401 Unauthorized
  • All MCP endpoints require authentication

Section 6: Ownership CRUD Operations

Case User Action Resource Owner Resource Visibility Expected
O-01 User A UPDATE User A private ALLOW
O-02 User A DELETE User A private ALLOW
O-03 User B UPDATE User A private DENY
O-04 User B DELETE User A private DENY
O-05 Team Owner UPDATE User B team ALLOW
O-06 Team Member UPDATE User B team DENY (not owner)
O-03: Non-owner cannot update private resource

Preconditions:

  • tool-priv-a owned by User A (visibility=private)
  • User B authenticated

Steps:

curl -X PATCH "http://localhost:4444/tools/tool-priv-a" \
  -H "Authorization: Bearer $USER_B_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"description": "Hacked description"}'

Expected Result:

  • HTTP 403 Forbidden
  • Only owner can modify private resources

Section 7: All Resource Types

Repeat visibility and ownership tests for each resource type:

Resource Type List Endpoint Get Endpoint Create Update Delete
Tools GET /tools GET /tools/{id} POST /tools PATCH /tools/{id} DELETE /tools/{id}
Resources GET /resources GET /resources/{id} POST /resources PATCH /resources/{id} DELETE /resources/{id}
Prompts GET /prompts GET /prompts/{id} POST /prompts PATCH /prompts/{id} DELETE /prompts/{id}
Servers GET /servers GET /servers/{id} POST /servers PATCH /servers/{id} DELETE /servers/{id}
Gateways GET /gateways GET /gateways/{id} POST /gateways PATCH /gateways/{id} DELETE /gateways/{id}
A2A Agents GET /a2a/agents GET /a2a/agents/{id} POST /a2a/agents PATCH /a2a/agents/{id} DELETE /a2a/agents/{id}

🧾 Complete Test Matrix

Access Control Matrix

# Auth is_admin Token Teams Visibility Resource Team Owner Match Expected
1 yes false [] public any n/a ALLOW
2 yes false [] team T n/a DENY
3 yes false [] private T yes DENY
4 yes false [T] team T n/a ALLOW
5 yes false [T] team X n/a DENY
6 yes false [T] private T yes ALLOW
7 yes false [T] private T no DENY
8 yes true null private any n/a ALLOW
9 no n/a n/a public any n/a ALLOW (permissive)
10 no n/a n/a team any n/a DENY
11 no n/a n/a private any n/a DENY
12 no n/a n/a any any n/a DENY (strict)

Team Filter Matrix

# Auth Token Teams Filter Resource Team Visibility Owner Expected
13 yes [T] T T team n/a LIST
14 yes [T] T T public n/a LIST
15 yes [T] T T private yes LIST
16 yes [T] T T private no DROP
17 yes [T] T X public n/a DROP
18 yes [] T T public n/a LIST
19 yes [] T T private yes DROP
20 yes [X] T T team n/a DROP (not member)

Token Scope Matrix

# RBAC Grants Token Permissions Expected
21 yes null/empty (inherit) ALLOW
22 yes excludes action DENY
23 no includes action DENY
24 yes includes action ALLOW

✅ Success Criteria

  • Coverage: All 6 resource types tested with all visibility levels
  • Token Types: Admin, team-scoped, and public-only tokens validated
  • Team Filters: Team filter semantics verified (excludes other teams' public resources)
  • Token Scopes: Intersection with RBAC confirmed (token restricts, cannot expand)
  • MCP Modes: Permissive and strict authentication modes validated
  • Ownership: CRUD operations respect owner/team admin rules
  • Reproducibility: Any tester can execute the plan with deterministic results
  • Documentation: All test cases documented with curl examples

🏁 Definition of Done

  • Manual test plan reviewed by QA and Security teams
  • All preconditions documented with setup scripts
  • All test cases have step-by-step procedures
  • All test cases have expected results with HTTP status codes
  • Test matrix covers all combinations from https://ibm.github.io/mcp-context-forge/architecture/multitenancy/
  • Token generation scripts provided and validated
  • MCP permissive/strict mode tests included
  • All 6 resource types covered (tools, resources, prompts, servers, gateways, A2A)
  • Plan successfully executed against Release 1.0.0-RC1
  • Results documented and any failures triaged
  • Linked as prerequisite for automated regression suite (#TBD)

📋 Automation Roadmap

After manual validation, convert to automated tests:

Phase 1: Pytest Fixtures

  • Create conftest.py fixtures for test users, teams, tokens
  • Create resource factory fixtures for all 6 types
  • Create token generation fixtures

Phase 2: Parametrized Tests

  • Convert access matrix to @pytest.mark.parametrize
  • Convert team filter matrix to parametrized tests
  • Convert token scope matrix to parametrized tests

Phase 3: Integration Tests

  • Test against live database (SQLite/PostgreSQL)
  • Test MCP SSE and WebSocket transports
  • Add to CI/CD pipeline

🔗 References


📝 Notes

🔹 Key Distinction: team_id is organizational ownership (where resource lives), visibility controls who can access. A public resource in Team T is accessible to all authenticated users, but still "belongs" to Team T.

🔹 Public-Only Token: When teams=[], the token holder can ONLY access public resources. This is true even if they are the owner of a private resource - the token scope overrides ownership.

🔹 Team Filter Semantics: When filtering by team_id=T, only resources with team_id=T are returned. Public resources from other teams are excluded from the results. This is filtering (which team's resources to show), not access control (whether user can see them).

🔹 Token Scope Intersection: Token permissions INTERSECT with RBAC grants. If RBAC grants tools.execute but token only has ["tools.read"], execute is denied. If token has ["tools.execute"] but RBAC doesn't grant it, execute is still denied.


📋 Implementation References (Verified)

Visibility Model Clarification:

  • In permissive mode (MCP_REQUIRE_AUTH=false), unauthenticated /mcp requests get teams=[]
  • Services use token_teams=[] to restrict to public resources only
  • Evidence: streamablehttp_transport.py:1367-1373, tool_service.py:1744-1758

Admin Token Nuance:

  • Admin token bypass (is_admin=true, teams=null) works when the teams claim is missing or null
  • Tokens created via the Token Catalog always set a list (teams=[] or specific team IDs)
  • Evidence: streamablehttp_transport.py:1242-1258, token_catalog_service.py:266-268
  • Test expectations should account for token shapes that yield unrestricted access

Metadata

Metadata

Assignees

Labels

MUSTP1: Non-negotiable, critical requirements without which the product is non-functional or unsafechoreLinting, formatting, dependency hygiene, or project maintenance choresmanual-testingManual testing / test planning issuesrbacRole-based Access ControlreadyValidated, ready-to-work-on itemssecurityImproves securitytestingTesting (unit, e2e, manual, automated, etc)

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions