Skip to content

[Feature]: Finalize RBAC / ABAC implementation to Implement Ownership Checks for Public Resources #1209

@crivetimihai

Description

@crivetimihai

Implement Ownership Checks for Public Resources

Problem

Currently, when a user creates a resource with visibility: public, any authenticated user can delete or edit that resource. The expected behavior is that only the owner (or team admin for team resources) should be able to modify or delete resources, regardless of visibility level.

Visibility should control read access, not write access:

  • private: Only owner can see/use/edit/delete
  • team: Team members can see/use, only owner/admin can edit/delete
  • public: Anyone can see/use, only owner/admin can edit/delete ← needs implementation

Current State

The system has all necessary infrastructure:

  • ✅ Ownership tracking fields (owner_email, team_id, visibility)
  • ✅ Permission service with RBAC capabilities
  • ✅ User-scoped list methods (mostly implemented)

Missing implementation:

  • ❌ Ownership validation in delete/update operations
  • ❌ Admin UI gateway list uses unfiltered method

Scope

Affected Entities (6):

  1. Gateways
  2. Virtual Servers
  3. Tools
  4. Resources
  5. Prompts
  6. A2A Agents

Operations (2 per entity):

  • Delete operations
  • Update operations

Interfaces (2):

  • REST API endpoints
  • Admin UI routes

Implementation Tasks

1. Service Layer - Add Ownership Validation

Add ownership check to delete/update methods in:

  • mcpgateway/services/gateway_service.py (lines 1023, 1534)
  • mcpgateway/services/server_service.py (lines 657, 931)
  • mcpgateway/services/tool_service.py (lines 704, 1122)
  • mcpgateway/services/resource_service.py (lines 933, 1053)
  • mcpgateway/services/prompt_service.py (lines 729, 944)
  • mcpgateway/services/a2a_service.py (lines 369, 467)

Pattern to implement:

async def delete_X(self, db: Session, x_id: str, user_email: str) -> None:
    resource = db.get(DbX, x_id)
    if not resource:
        raise NotFoundError(...)

    # Check ownership
    if resource.owner_email != user_email:
        # Allow team admins for team resources
        if resource.visibility == "team" and resource.team_id:
            team_service = TeamManagementService(db)
            user_role = await team_service.get_user_role(user_email, resource.team_id)
            if user_role != "owner":
                raise PermissionError("Only the owner or team admin can delete this resource")
        else:
            raise PermissionError("Only the owner can delete this resource")

    db.delete(resource)
    db.commit()

2. API Layer - Pass User Email to Services

Update REST API endpoints in mcpgateway/main.py:

  • Delete endpoints: 1543, 2286, 2644, 2987, 3233
  • Update endpoints: (corresponding lines for PUT operations)

Pattern:

async def delete_gateway(
    gateway_id: str,
    db: Session = Depends(get_db),
    user=Depends(get_current_user_with_permissions)
):
    await gateway_service.delete_gateway(
        db,
        gateway_id,
        user_email=user['email']  # ← Add this
    )

3. Admin UI - Fix Gateway List Method

File: mcpgateway/admin.py:1759

Change:

gateways = await gateway_service.list_gateways(db, include_inactive=include_inactive)

To:

user_email = get_user_email(user)
gateways = await gateway_service.list_gateways_for_user(db, user_email, include_inactive=include_inactive)

Note: The list_gateways_for_user() method already exists (line 953 in gateway_service.py).

4. Admin UI - Update Delete Routes

Update admin delete routes to pass user email:

  • admin_delete_server (line 1353)
  • admin_delete_tool (line 5354)
  • admin_delete_gateway (line 6202)
  • admin_delete_resource (line 6627)
  • admin_delete_prompt (line 7134)
  • admin_delete_a2a_agent (line 9093)

5. Admin UI - Conditionally Render Delete Buttons

Update HTML template generation to only show delete/edit buttons for owned resources:

# Check ownership before rendering button
user_email = get_user_email(user)
if resource.owner_email == user_email:
    delete_button_html = f'<button hx-delete="...">Delete</button>'
else:
    delete_button_html = ''  # No button for non-owned resources

6. Permission Service - Add Resource Ownership Helper

Implement the TODO at line 255 in permission_service.py:

async def check_resource_ownership(
    self,
    user_email: str,
    resource: Any,
    team_id: Optional[str] = None
) -> bool:
    """Check if user owns resource or is team admin."""
    # Check direct ownership
    if resource.owner_email == user_email:
        return True

    # Check team admin for team resources
    if resource.visibility == "team" and resource.team_id:
        return await self._is_team_admin(user_email, resource.team_id)

    return False

Acceptance Criteria

Backend

  • User A cannot delete User B's public gateway
  • User A cannot update User B's public server
  • User A can delete their own public resources
  • Team owner can delete team member's team resources
  • Team member cannot delete team owner's team resources
  • All 6 entity types enforce ownership on delete and update

Admin UI

  • Gateway list only shows gateways user has access to
  • Delete buttons only appear for resources user owns
  • Update buttons only appear for resources user owns

REST API

  • DELETE /gateways/{id} returns 403 for non-owned resources
  • PUT /servers/{id} returns 403 for non-owned resources
  • Appropriate error messages for permission denials

Notes

  • The Permission Service infrastructure already exists and works correctly
  • Most list methods already filter by user (tools, servers, resources, prompts)
  • Create operations already set ownership correctly
  • This change only affects delete and update operations
  • Private resources already work correctly (not visible to other users)

Files Summary

Service Layer: 6 files
API Layer: main.py (12+ endpoints)
Admin UI: admin.py (1 list fix + 6 delete routes + HTML templates)
Permission Service: permission_service.py (add helper method)

References

  • Investigation Report: todo/rbac-issues.md (detailed analysis with line numbers)
  • Permission Service: mcpgateway/services/permission_service.py
  • Database Models: mcpgateway/db.py
  • Schemas: mcpgateway/schemas.py

RBAC Ownership Implementation Tracker

Service Layer - Add Ownership Checks

Gateway Service (gateway_service.py)

  • delete_gateway (line 1534) - add user_email param + ownership check
  • update_gateway (line 1023) - add ownership check

Server Service (server_service.py)

  • delete_server (line 931) - add user_email param + ownership check
  • update_server (line 657) - add ownership check (has user_email already)

Tool Service (tool_service.py)

  • delete_tool (line 704) - add user_email param + ownership check
  • update_tool (line 1122) - add ownership check

Resource Service (resource_service.py)

  • delete_resource (line 1053) - add user_email param + ownership check
  • update_resource (line 933) - add ownership check

Prompt Service (prompt_service.py)

  • delete_prompt (line 944) - add user_email param + ownership check
  • update_prompt (line 729) - add ownership check

A2A Service (a2a_service.py)

  • delete_agent (line 467) - add user_email param + ownership check
  • update_agent (line 369) - add ownership check

API Layer - Pass User Email (main.py)

Delete Endpoints

  • delete_server (line 1543)
  • delete_tool (line 2286)
  • delete_resource (line 2644)
  • delete_prompt (line 2987)
  • delete_gateway (line 3233)
  • delete_a2a_agent (find line)

Update Endpoints

  • update_server (find line)
  • update_tool (find line)
  • update_resource (find line)
  • update_prompt (find line)
  • update_gateway (find line)
  • update_a2a_agent (find line)

Admin UI Layer (admin.py)

List Methods

  • Fix admin_list_gateways (line 1759) - use list_gateways_for_user

Delete Routes - Pass User Email

  • admin_delete_server (line 1353)
  • admin_delete_tool (line 5354)
  • admin_delete_gateway (line 6202)
  • admin_delete_resource (line 6627)
  • admin_delete_prompt (line 7134)
  • admin_delete_a2a_agent (line 9093)

HTML Templates - Conditional Buttons

  • Server list - check ownership before rendering delete button
  • Tool list - check ownership before rendering delete button
  • Gateway list - check ownership before rendering delete button
  • Resource list - check ownership before rendering delete button
  • Prompt list - check ownership before rendering delete button
  • A2A list - check ownership before rendering delete button

Permission Service

  • Add check_resource_ownership helper method (line 255 TODO)

Testing

Unit Tests

  • User A cannot delete User B's public resource
  • User A can delete their own public resource
  • Team admin can delete team member's team resource
  • Team member cannot delete team owner's team resource

Integration Tests

  • REST API returns 403 for non-owned delete
  • REST API returns 403 for non-owned update
  • Admin UI blocks non-owned deletes

UI Tests

  • Delete buttons only show for owned resources
  • Gateway list filtered by user access

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requestsecurityImproves securitytriageIssues / Features awaiting triage

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions