Files
gt-ai-os-community/apps/tenant-backend/app/api/v1/access_groups.py
HackWeasel 310491a557 GT AI OS Community v2.0.33 - Add NVIDIA NIM and Nemotron agents
- Updated python_coding_microproject.csv to use NVIDIA NIM Kimi K2
- Updated kali_linux_shell_simulator.csv to use NVIDIA NIM Kimi K2
  - Made more general-purpose (flexible targets, expanded tools)
- Added nemotron-mini-agent.csv for fast local inference via Ollama
- Added nemotron-agent.csv for advanced reasoning via Ollama
- Added wiki page: Projects for NVIDIA NIMs and Nemotron
2025-12-12 17:47:14 -05:00

411 lines
14 KiB
Python

"""
Access Groups API for GT 2.0 Tenant Backend
RESTful API endpoints for managing resource access groups and permissions.
"""
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Header, Query
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db_session
from app.core.security import get_current_user, verify_capability_token
from app.models.access_group import (
AccessGroup, ResourceCreate, ResourceUpdate, ResourceResponse,
AccessGroupModel
)
from app.services.access_controller import AccessController, AccessControlMiddleware
router = APIRouter(
prefix="/api/v1/access-groups",
tags=["access-groups"],
responses={404: {"description": "Not found"}},
)
async def get_access_controller(
x_tenant_domain: str = Header(..., description="Tenant domain")
) -> AccessController:
"""Dependency to get access controller for tenant"""
return AccessController(x_tenant_domain)
async def get_middleware(
x_tenant_domain: str = Header(..., description="Tenant domain")
) -> AccessControlMiddleware:
"""Dependency to get access control middleware"""
return AccessControlMiddleware(x_tenant_domain)
@router.post("/resources", response_model=ResourceResponse)
async def create_resource(
resource: ResourceCreate,
authorization: str = Header(..., description="Bearer token"),
x_tenant_domain: str = Header(..., description="Tenant domain"),
controller: AccessController = Depends(get_access_controller),
db: AsyncSession = Depends(get_db_session)
):
"""
Create a new resource with access control.
- **name**: Resource name
- **resource_type**: Type of resource (agent, dataset, etc.)
- **access_group**: Access level (individual, team, organization)
- **team_members**: List of user IDs for team access
- **metadata**: Resource-specific metadata
"""
try:
# Extract bearer token
if not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Invalid authorization header")
capability_token = authorization.replace("Bearer ", "")
# Get current user
user = await get_current_user(authorization, db)
# Create resource
created_resource = await controller.create_resource(
user_id=user.id,
resource_data=resource,
capability_token=capability_token
)
return ResourceResponse(
id=created_resource.id,
name=created_resource.name,
resource_type=created_resource.resource_type,
owner_id=created_resource.owner_id,
tenant_domain=created_resource.tenant_domain,
access_group=created_resource.access_group,
team_members=created_resource.team_members,
created_at=created_resource.created_at,
updated_at=created_resource.updated_at,
metadata=created_resource.metadata,
file_path=created_resource.file_path
)
except PermissionError as e:
raise HTTPException(status_code=403, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to create resource: {str(e)}")
@router.put("/resources/{resource_id}/access", response_model=ResourceResponse)
async def update_resource_access(
resource_id: str,
access_update: AccessGroupModel,
authorization: str = Header(..., description="Bearer token"),
x_tenant_domain: str = Header(..., description="Tenant domain"),
controller: AccessController = Depends(get_access_controller),
middleware: AccessControlMiddleware = Depends(get_middleware),
db: AsyncSession = Depends(get_db_session)
):
"""
Update resource access group.
Only the resource owner can change access settings.
- **access_group**: New access level
- **team_members**: Updated team members list (for team access)
"""
try:
# Get current user
user = await get_current_user(authorization, db)
capability_token = authorization.replace("Bearer ", "")
# Verify permission
await middleware.verify_request(
user_id=user.id,
resource_id=resource_id,
action="share",
capability_token=capability_token
)
# Update access
updated_resource = await controller.update_resource_access(
user_id=user.id,
resource_id=resource_id,
new_access_group=access_update.access_group,
team_members=access_update.team_members
)
return ResourceResponse(
id=updated_resource.id,
name=updated_resource.name,
resource_type=updated_resource.resource_type,
owner_id=updated_resource.owner_id,
tenant_domain=updated_resource.tenant_domain,
access_group=updated_resource.access_group,
team_members=updated_resource.team_members,
created_at=updated_resource.created_at,
updated_at=updated_resource.updated_at,
metadata=updated_resource.metadata,
file_path=updated_resource.file_path
)
except PermissionError as e:
raise HTTPException(status_code=403, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to update access: {str(e)}")
@router.get("/resources", response_model=List[ResourceResponse])
async def list_accessible_resources(
resource_type: Optional[str] = Query(None, description="Filter by resource type"),
authorization: str = Header(..., description="Bearer token"),
x_tenant_domain: str = Header(..., description="Tenant domain"),
controller: AccessController = Depends(get_access_controller),
db: AsyncSession = Depends(get_db_session)
):
"""
List all resources accessible to the current user.
Returns resources based on access groups:
- Individual: Only owned resources
- Team: Resources shared with user's teams
- Organization: All organization-wide resources
"""
try:
# Get current user
user = await get_current_user(authorization, db)
# Get accessible resources
resources = await controller.list_accessible_resources(
user_id=user.id,
resource_type=resource_type
)
return [
ResourceResponse(
id=r.id,
name=r.name,
resource_type=r.resource_type,
owner_id=r.owner_id,
tenant_domain=r.tenant_domain,
access_group=r.access_group,
team_members=r.team_members,
created_at=r.created_at,
updated_at=r.updated_at,
metadata=r.metadata,
file_path=r.file_path
)
for r in resources
]
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to list resources: {str(e)}")
@router.get("/resources/{resource_id}", response_model=ResourceResponse)
async def get_resource(
resource_id: str,
authorization: str = Header(..., description="Bearer token"),
x_tenant_domain: str = Header(..., description="Tenant domain"),
controller: AccessController = Depends(get_access_controller),
middleware: AccessControlMiddleware = Depends(get_middleware),
db: AsyncSession = Depends(get_db_session)
):
"""
Get a specific resource if user has access.
Checks read permission based on access group.
"""
try:
# Get current user
user = await get_current_user(authorization, db)
capability_token = authorization.replace("Bearer ", "")
# Verify permission
await middleware.verify_request(
user_id=user.id,
resource_id=resource_id,
action="read",
capability_token=capability_token
)
# Load resource
resource = await controller._load_resource(resource_id)
return ResourceResponse(
id=resource.id,
name=resource.name,
resource_type=resource.resource_type,
owner_id=resource.owner_id,
tenant_domain=resource.tenant_domain,
access_group=resource.access_group,
team_members=resource.team_members,
created_at=resource.created_at,
updated_at=resource.updated_at,
metadata=resource.metadata,
file_path=resource.file_path
)
except PermissionError as e:
raise HTTPException(status_code=403, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to get resource: {str(e)}")
@router.delete("/resources/{resource_id}")
async def delete_resource(
resource_id: str,
authorization: str = Header(..., description="Bearer token"),
x_tenant_domain: str = Header(..., description="Tenant domain"),
controller: AccessController = Depends(get_access_controller),
middleware: AccessControlMiddleware = Depends(get_middleware),
db: AsyncSession = Depends(get_db_session)
):
"""
Delete a resource.
Only the resource owner can delete.
"""
try:
# Get current user
user = await get_current_user(authorization, db)
capability_token = authorization.replace("Bearer ", "")
# Verify permission
await middleware.verify_request(
user_id=user.id,
resource_id=resource_id,
action="delete",
capability_token=capability_token
)
# Delete resource (implementation needed)
# await controller.delete_resource(resource_id)
return {"status": "deleted", "resource_id": resource_id}
except PermissionError as e:
raise HTTPException(status_code=403, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to delete resource: {str(e)}")
@router.get("/stats")
async def get_resource_stats(
authorization: str = Header(..., description="Bearer token"),
x_tenant_domain: str = Header(..., description="Tenant domain"),
controller: AccessController = Depends(get_access_controller),
db: AsyncSession = Depends(get_db_session)
):
"""
Get resource statistics for the current user.
Returns counts by type and access group.
"""
try:
# Get current user
user = await get_current_user(authorization, db)
# Get stats
stats = await controller.get_resource_stats(user_id=user.id)
return stats
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to get stats: {str(e)}")
@router.post("/resources/{resource_id}/team-members/{user_id}")
async def add_team_member(
resource_id: str,
user_id: str,
authorization: str = Header(..., description="Bearer token"),
x_tenant_domain: str = Header(..., description="Tenant domain"),
controller: AccessController = Depends(get_access_controller),
middleware: AccessControlMiddleware = Depends(get_middleware),
db: AsyncSession = Depends(get_db_session)
):
"""
Add a user to team access for a resource.
Only the resource owner can add team members.
Resource must have team access group.
"""
try:
# Get current user
current_user = await get_current_user(authorization, db)
capability_token = authorization.replace("Bearer ", "")
# Verify permission
await middleware.verify_request(
user_id=current_user.id,
resource_id=resource_id,
action="share",
capability_token=capability_token
)
# Load resource
resource = await controller._load_resource(resource_id)
# Check if team access
if resource.access_group != AccessGroup.TEAM:
raise HTTPException(
status_code=400,
detail="Resource must have team access to add members"
)
# Add team member
resource.add_team_member(user_id)
# Save changes (implementation needed)
# await controller.save_resource(resource)
return {"status": "added", "user_id": user_id, "resource_id": resource_id}
except PermissionError as e:
raise HTTPException(status_code=403, detail=str(e))
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to add team member: {str(e)}")
@router.delete("/resources/{resource_id}/team-members/{user_id}")
async def remove_team_member(
resource_id: str,
user_id: str,
authorization: str = Header(..., description="Bearer token"),
x_tenant_domain: str = Header(..., description="Tenant domain"),
controller: AccessController = Depends(get_access_controller),
middleware: AccessControlMiddleware = Depends(get_middleware),
db: AsyncSession = Depends(get_db_session)
):
"""
Remove a user from team access for a resource.
Only the resource owner can remove team members.
"""
try:
# Get current user
current_user = await get_current_user(authorization, db)
capability_token = authorization.replace("Bearer ", "")
# Verify permission
await middleware.verify_request(
user_id=current_user.id,
resource_id=resource_id,
action="share",
capability_token=capability_token
)
# Load resource
resource = await controller._load_resource(resource_id)
# Remove team member
resource.remove_team_member(user_id)
# Save changes (implementation needed)
# await controller.save_resource(resource)
return {"status": "removed", "user_id": user_id, "resource_id": resource_id}
except PermissionError as e:
raise HTTPException(status_code=403, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to remove team member: {str(e)}")